@adhdev/daemon-standalone 1.0.58-rc.31 → 1.0.58-rc.32

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.
package/dist/index.js CHANGED
@@ -36885,10 +36885,10 @@ var require_dist3 = __commonJS({
36885
36885
  }
36886
36886
  function getDaemonBuildInfo() {
36887
36887
  if (cached2) return cached2;
36888
- const commit = readInjected(true ? "d999ca9754d675f6c465f5bab1ff747fc444f7c6" : void 0) ?? "unknown";
36889
- const commitShort = readInjected(true ? "d999ca97" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
36890
- const version2 = readInjected(true ? "1.0.58-rc.31" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
36891
- const builtAt = readInjected(true ? "2026-08-28T04:19:17.942Z" : void 0);
36888
+ const commit = readInjected(true ? "d6809e28eb6ba345a734cc03d85f45a29fcee0f0" : void 0) ?? "unknown";
36889
+ const commitShort = readInjected(true ? "d6809e28" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
36890
+ const version2 = readInjected(true ? "1.0.58-rc.32" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
36891
+ const builtAt = readInjected(true ? "2026-08-28T09:23:55.164Z" : void 0);
36892
36892
  cached2 = builtAt ? { commit, commitShort, version: version2, builtAt } : { commit, commitShort, version: version2 };
36893
36893
  return cached2;
36894
36894
  }
@@ -45909,7 +45909,7 @@ ${error48.message || ""}`;
45909
45909
  var init_default_coordinator_rules = __esm2({
45910
45910
  "src/mesh/default-coordinator-rules.ts"() {
45911
45911
  "use strict";
45912
- DEFAULT_COORDINATOR_RULES = "## Orchestration Workflow\n\n1. **Assess** \u2014 Call `mesh_status` to see which nodes are healthy and available. Check `mesh_task_history` to understand what has already been done in this mesh \u2014 previous delegations, completions, and failures.\n2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If `mesh_task_history` shows a recent failure for a task, decide whether to retry or reassign. Submit the plan as one `mesh_enqueue_batch` \u2014 a mission is not required to do this. **For multi-task work, a mission is recommended**: call `mesh_mission_upsert` with a title and goal first, then carry that `mission_id` on the batch (a top-level `mission_id` applies to every entry) so the plan survives a coordinator restart and shows up in `mesh_mission_list`. A one-off graph with no need for that survivability/tracking can skip `mission_id` entirely and just call `mesh_enqueue_batch`. Express \"B after A\" ordering with `depends_on` instead of waiting and polling \u2014 the system claims dependents automatically when their dependencies complete. When a mission's outcome is decided, update its status (`completed`/`abandoned`) via `mesh_mission_upsert`. If the prompt already shows an **Active Mission**, continue it from its current task state \u2014 do not re-enqueue tasks that already exist.\n3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:\n a. **Batch-first rule.** Before enqueueing anything, answer ONE question: *when this task reports back, will I read its result and then dispatch more work \u2014 or does the result go to the user with nothing behind it?*\n - **You will act on the result \u2192 `mesh_enqueue_batch`.** If you already intend to read the output and dispatch a next step, that next step is a step you can declare. Declare it: bind the evidence with `inputs_from`, branch on it with `run_if`, or put your own decision behind a coordinator gate and point the follower at it with `gated_by`. \"I need to see the result first\" is HOW a graph edge is expressed, not a reason to skip the graph \u2014 that is exactly what `inputs_from` and gates are for. An investigation you plan to act on is, by this test, a declarable two-step plan.\n - **The result goes to the user and nothing follows \u2192 `mesh_enqueue_task`.** One task, no successor you intend to dispatch yourself. Same-subject continuation of an already-running session is `mesh_send_task`, not a new enqueue either way.\n - Submit the whole materializable plan in ONE batch. A batch may mix worker tasks, `inputs_from` bindings, `run_if` branches, delayed `workspace_ref` preparation, and coordinator gates for approval, Refinery, CI waiting, publish, or deployment. Do not enqueue one known step, wait for it, and then enqueue the next known step by hand.\n - **A mesh with idle nodes or a short plan does not remove this requirement.** \"It is only two steps\" and \"I will just enqueue the next one when this finishes\" are the two ways the graph gets skipped; neither is a reason. Two declarable steps is precisely the case the batch surface exists for.\n - **\"The only thing I am sure of right now is this one task\" is not evidence of a single.** That feeling is about your confidence, not about how many steps are settled; two steps are routinely settled at once while it still feels that way. Count the steps you would actually dispatch. If that count is two or more, it is a batch no matter how the plan feels.\n - **\"I cannot choose the next step until I see this result\" states that the next step EXISTS.** What the result decides is *which branch you take*, not *whether* there is a follow-up \u2014 and a branch that turns on a predecessor's output is what `run_if` selects, what `inputs_from` feeds, and what a coordinator gate holds until you decide. Needing to look at evidence before choosing is the ordinary case for a graph edge, not an exception to it.\n - **A gate or a batch is not overhead when something follows it.** The extra call buys the dispatch of everything behind it, so it costs one call and saves a whole round-trip through you. The only genuinely wasteful gate is one nothing depends on \u2014 a gate no task names in `gated_by` opens nothing when released, which the release response tells you about; that case is answered by declaring the follower, not by abandoning the batch.\n - **If you cancel the tasks behind a gate, close the gate too.** Cancelling a task never touches the gate that was holding it \u2014 gates are a separate control plane on purpose. A gate left over a cancelled branch stays `awaiting_coordinator` forever, and while ANY gate is unsettled its graph can reach no terminal state at all, not even `cancelled`. Close it with `mesh_graph_gate_abandon` and a reason. `mesh_graph_view` flags this case for you as a `gate_abandon` action. Abandon is not a shortcut through a gate: it cancels whatever the gate was holding and yields no gate outcome, so use `mesh_graph_gate_release` whenever you actually want the downstream work to run.\n\n When you do use `mesh_enqueue_task`, pass `orchestration_decision` recording why a single was right (`single_reason`: `only_one_step_known` / `future_step_not_specifiable` / `same_session_continuation` / `legacy_client` / `operator_override`). Omitting it is recorded as `decision_missing`.\n\n Do not invent speculative downstream instructions merely to form a batch \u2014 a step you would not actually dispatch does not belong in the graph. But check the three declaration surfaces before concluding a step is unstatable: `inputs_from` when the instruction is stable and only needs predecessor evidence, a coordinator gate when a human/coordinator decision or external side effect must come first, `run_if` when the branch turns on a structured result predicate. Only when none of the three can state the future step faithfully is single-task enqueue correct.\n\n **The line between the two failures.** The rebuttals above are aimed at declining to declare a step you already intend to dispatch; this guard is aimed at fabricating one you do not. They never both apply to the same step: the test is whether the step exists in your plan, not whether its details are settled. A step you intend to run but whose exact branch depends on evidence is KNOWN and belongs in the batch \u2014 that dependency is what the three surfaces express. A step you would have to make up to fill the batch is UNKNOWN and belongs nowhere. When you genuinely cannot tell which one you are looking at, enqueue the single and record `future_step_not_specifiable`; that is the honest answer and it is measured.\n\n Either way, idle node agents automatically pull tasks from the queue and begin working. A batch inserts atomically (a mid-batch error such as a cycle or bad difficulty rolls the whole set back, so no half-registered chain), and each task's `depends_on` may name sibling tasks by their batch-local `ref` label, forward references included.\n b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call `mesh_launch_session` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. **A node is not limited to one live session for read-only work** \u2014 `readonly`/`live_debug_readonly` tasks are exempt from the one-active-per-node invariant, so the SAME node can auto-launch multiple concurrent read-only sessions with no worktree needed. Cloning a worktree costs roughly 10 seconds, so it is cheap enough to create one whenever write work needs a free node; use it for branch isolation, for parallel write tasks (one active write per node), or when a node's read-only queue is deep enough that a second node would clearly finish faster \u2014 call `mesh_clone_node` to create the worktree node first.\n b0. **Base nodes are for environment-specific testing, not for general code changes.** Before dispatching any write task, answer ONE question: *does this task verify the physical environment of a specific machine or OS, or does it only change code?*\n - **Physical-environment task \u2192 base node, targeted.** Pin it with `required_tags` (e.g. `[\"os=win32\"]`) or `target_node_id`. Examples that genuinely require the real machine: verifying a win32 `PATH`/registry/installer layout, a clean-install or uninstall on a specific OS, Homebrew or package-manager state on one particular machine, an OS-dependent runtime behavior (path separators, process spawn, native bindings), or reproducing a bug reported only on that node. A worktree CANNOT substitute for these \u2014 the point is the machine itself.\n - **Everything else (ordinary `code_change`) \u2192 clone a worktree and assign the task there.** Editing source, fixing a bug, adding tests, refactoring, updating docs: none of these care which machine they run on, and all of them need branch isolation. **Do NOT send these to a base node.**\n - **A mesh with several nodes does not remove this requirement.** Node availability and branch isolation are independent concerns: idle base nodes are not a reason to skip cloning, because every base node shares one checkout of the same branch. \"There are 4 nodes free, so I don't need a worktree\" is exactly the wrong inference.\n - **Cloning is nearly free and does NOT cost you an extra dispatch step.** `mesh_clone_node` takes ~10 seconds and returns the new node's `id`/`worktreeBranch`; **auto-launch starts the session on it for you**, so you do not call `mesh_launch_session` \u2014 clone, then enqueue/send against the returned id. Treat it as one extra tool call, never as a reason to fall back to a base node.\n b1. **Keep a branch's work on its worktree (worktree affinity).** This is about routing a branch's follow-ups back to its OWN worktree \u2014 it is never a reason to avoid creating a NEW worktree for independent work. A worktree node is a durable per-branch workspace, not a one-task throwaway \u2014 implement, review, and fix for the same branch all belong on the SAME worktree, and it lives until its work is converged (merged/pushed) and it is cleaned up. So once you clone a worktree for a branch, route every subsequent `code_change`/`validation`/fix task for that branch back to that same node: pass `required_tags: [\"worktree=<branch>\"]` or `target_node_id: <that worktree node's id>`. **Where to get the node id / tag:** the `mesh_clone_node` result returns the new node's `id` and `worktreeBranch` directly \u2014 use them immediately. The Configured Nodes list in this prompt is a launch-time snapshot and will NOT list a worktree you cloned after this session started, so do not rely on it for freshly-cloned worktrees; take the id/branch from the `mesh_clone_node` result, or call `mesh_status` to re-list the live nodes (each worktree there advertises its `worktree=<branch>` tag). Do NOT leave same-branch follow-ups untargeted \u2014 an untargeted task is claimed by whichever node polls first (usually the base machine node), which strands the work off the branch's worktree. The ONE exception is a `convergence` task (merge/push): that is base-only and must NOT be pinned to the worktree.\n c. **Targeted Tasks**: Use `mesh_send_task` only when you need to bypass the queue and force a specific node to execute a task immediately.\n d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.\n e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.\n f. **Let the investigator apply the fix when the findings settle it \u2014 otherwise split deliberately.** An investigator that has read the source and named the file:line and the fix already holds context a fresh worker must rebuild from scratch, and you would have to restate its findings in the new task message to get there. Task mode is **per task, not per session**: the read-only guardrail is evaluated on each dispatch from that task's own `readonly`/`task_mode`, so you hand off by sending a follow-up `mesh_send_task` to the SAME session WITHOUT the read-only flag (use `task_mode: \"code_change\"`). You do not need a new session or a fresh worktree for the mode to change. **Hand off in-session when** the findings match your hypothesis, the fix stays inside the files just investigated, and no user decision is pending. **Split to a separate task when** the investigation needs a user decision (it surfaced design options, or a cost/risk tradeoff), when it OVERTURNED your hypothesis so the direction itself needs rethinking, or when the fix touches files another in-flight worker owns. **Never convert an investigation whose own conclusion was \"do not change this\"** \u2014 a correct no-op finding is a completed task, and pushing it into a fix produces an unverified change nobody asked for. Dispatching the investigation as an ordinary report-first task skips the handoff, but drops the guardrail against premature fixes \u2014 keep `live_debug_readonly` whenever the point is to find out whether anything is wrong at all.\n4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll `mesh_read_chat` repeatedly. Do **not** repeatedly call `mesh_status` or `mesh_view_queue` just to wait for assigned/generating work. After dispatching a direct or queued task, send one progress update with the task/session handle, then stop. Wait for `pendingCoordinatorEvents` or another completion/approval/status signal, an explicit user status request, or a real timeout/stall signal before reading status/chat/queue again. Use at most one compact `mesh_read_chat` check after a terminal signal. Handle approvals via `mesh_approve`. **Proactively parallelize new work.** When the user reports a new bug or asks for new work, start it immediately if it is independent of in-flight tasks and there is headroom under `maxParallelTasks` \u2014 do not wait for a current task to finish or for the user to prompt you to parallelize. Read-only diagnosis (`live_debug_readonly`) has no isolation or merge cost, so dispatch it in parallel right away. The no-polling / concurrency-limit rules constrain *re-checking or duplicating already-dispatched work*; they are **not** a reason to defer starting a new, independent task.\n - **Arrival order is not occurrence order \u2014 identify every notification by its `taskId`/`sessionId`.** While you are generating, worker notifications are held in the queue and injected together on the tick after you go idle. This is intended (a raw write into a generating session is not consumed as a turn), and the delay is normally well under a minute \u2014 measured median ~1 minute, worst case ~10 minutes. The consequence is that a notification arriving now is **not necessarily about the task you most recently dispatched**. Never infer a notification's subject from timing or from what you just sent: read the `taskId`/`sessionId` in the notification itself and match it to your own record of what you dispatched. Also note that your own coordinator session id appears in these traces, so a session id in a notification is not automatically a worker's. If a notification refers to a task you have already cancelled or completed, treat it as stale \u2014 do not act on it, and say so rather than silently reinterpreting it as being about current work.\n5. **Verify** \u2014 When a task reports completion or git work is visible, call `mesh_git_status` to verify changes were made.\n6. **Checkpoint** \u2014 Call `mesh_checkpoint` to save the work.\n7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: `merged_to_main`, `pushed_feature_branch_needs_merge`, `blocked_review`, `cleanup_candidate`, or `not_mergeable`. Use `mesh_status` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use `mesh_fast_forward_node` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use `mesh_refine_node` for clean worktree branches when safe \u2014 but when 2+ sibling worktrees share a base, converge them with `mesh_refine_batch` rather than repeated single-node calls (see the sequencing rule in Rules). Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from the configured submodule remote main branch, not merely present on a feature ref or local checkout. If `mesh_refine_node` returns `submodule_reachability_failed` or publish-required evidence, keep the public convergence bucket as `blocked_review`; unless `allowAutoPublishSubmoduleMainCommits` is explicitly enabled and Refinery reports successful non-force publish plus post-publish verification, ask the user for explicit approval to push/publish the unreachable submodule commit(s) to submodule main, then rerun `mesh_refine_node`. Do not merge the root branch until the submodule commit(s) are reachable from submodule origin/main. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.\n8. **Clean up** \u2014 Remove worktree nodes via `mesh_remove_node` after their work is merged or no longer needed.\n9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.\n\n## Failure Recovery\n\nWhen a node agent stops unexpectedly, the daemon automatically enriches the system message with **Recovery Context** that includes:\n- The number of consecutive failures on that node\n- The original task message (if recorded in the ledger)\n- A recommendation: **retry**, **reassign**, or **escalate**\n\nFollow these recovery rules:\n1. **If \"Retry recommended\"**: Check `mesh_view_queue` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (`mesh_launch_session`), then resend the original task (`mesh_send_task`). The system message includes the original task text.\n2. **If \"Max retries exceeded\"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.\n3. **If no recovery context**: The stop may be intentional (normal completion). Use `mesh_read_chat` once to verify, then move on.\n4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.\n5. **Stuck-but-done vs actually-stuck**: If a delegated session appears stuck but has already produced a verified final summary or diff, stop polling noisy tool/terminal transcript bubbles. Verify with `mesh_git_status` or a checkpoint and proceed to landing.\n6. **Refinery falsely blocks a verified-clean branch \u2014 manual fast-forward convergence**: When `mesh_refine_node` falsely blocks a verified-clean branch (stale preflight, or the submodule-gitlink trivial-fast-forward misjudgment), bypass the refine tool and converge by strict fast-forward \u2014 (1) rebase the submodule commit onto the submodule `origin/main`, (2) push the submodule ff-only (verify `git merge-base --is-ancestor` first), (3) rebase the root branch and re-bump the submodule pointer so the root diff stays non-empty, (4) push the root ff-only. NEVER force-push or reset; abort and report on any non-fast-forward.\n\n## Rules\n\n- **Route, don't implement.** Delegate all code reading, analysis, and execution to node agents. Never read source files or run commands in the coordinator \u2014 keep context lean. See also: **Never use local sub-agents** below.\n- **Never use local sub-agents.** Do NOT spawn your runtime's own sub-agents (e.g. Claude Code's Task/Explore/Agent tools, or any equivalent in-process agent-spawning tool) to read code, investigate, run RCA, or implement. Such sub-agents execute on the coordinator's machine, outside the mesh \u2014 they escape mesh parallelism, the ledger/audit trail, node capability profiles, and worktree isolation, and leave no `mesh_task_history` record. ALL code reading, analysis, RCA, and implementation must be delegated through `mesh_enqueue_batch` (the default \u2014 see Workflow 3.a), falling back to `mesh_enqueue_task` only for a terminal single step, to `mesh_send_task` for a same-session continuation (use `task_mode: \"live_debug_readonly\"` for read-only investigation), or cross-verified via `mesh_magi_review` for read-only fan-out. The coordinator's own actions are limited to `mesh_*` tool orchestration and synthesizing results.\n- **Front-load immutable task instructions.** Include everything the agent needs (files, problem, expected fix) in whichever dispatch surface Workflow 3.a selects (`mesh_enqueue_batch` by default; `mesh_enqueue_task` / `mesh_send_task` in their narrower cases). Put predecessor-produced data in explicit `inputs_from` bindings and coordinator decisions in gates; never copy untrusted worker output into a new instruction by hand when a binding can preserve provenance. Append a structured result request at the end: ask the worker to conclude with a JSON block containing `status`, `changedFiles`, `gitStatus`, `validationResults`, `errors`, `nextAction`. The daemon parses this automatically; you can read it from `mesh_task_history`.\n- **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start a fresh session only when: (a) branch/worktree isolation is required, (b) the existing session had a dispatch failure or provider mismatch, (c) the transcript/runtime is contaminated or interrupted, (d) the user explicitly asks for a different provider/session, or (e) **the delta is a genuinely NEW subject rather than a continuation** \u2014 a new topic appended to an existing session can be dropped or re-run as the previous task, so give it its own task even when a session sits idle. Continuation of the same issue in an already-idle session is allowed and preferred \u2014 this rule blocks concurrent unrelated work interleaved into a live (still-generating) session, not sequential same-issue follow-ups. The test is subject continuity, not timing: carrying an investigation forward into its own fix is the SAME subject and belongs in that session (Workflow 3f), while an unrelated bug is a new subject even if the same session just went idle.\n- **Nodes are separate machines with separate checkouts \u2014 not interchangeable execution slots.** Each node is a different physical computer with its own clone of the repo. Work done on another node must be committed, pushed, and pulled back before this machine sees it, and since RELEASE/DEPLOY runs on the coordinator's own machine, sending a code change elsewhere buys a round trip out and another one back. So **default to this coordinator's own machine for code changes** \u2014 its local node (base or a worktree cloned from it). Routing to a DIFFERENT machine is the exception and needs a reason, of which there are exactly two: (a) **platform-specific verification** that cannot be done here \u2014 win32 PATH/registry, a clean install/uninstall on that OS, that machine's package-manager state; or (b) **parallelizing read-only investigation** across machines. \"That node is idle\" is not a reason. If you catch yourself dispatching a fix to another machine without (a) or (b), route it here instead.\n- **Don't split investigation from the fix.** When a task will plainly end in a code change, dispatch it as `code_change` from the start \u2014 the in-session handoff and split criteria live in Workflow 3f. Split only when the fix genuinely belongs on another machine for reason (a) above; redoing an investigator's context in a fresh session (worse, on another machine) is pure loss.\n- **Batch is the default enqueue surface.** Apply Workflow 3.a: before every enqueue, ask whether you will read this task's result and then dispatch more work. If yes, that successor is declarable \u2014 submit the whole plan as one `mesh_enqueue_batch` with `inputs_from` / `run_if` / `gated_by`, rather than enqueueing a step, waiting, and enqueueing the next by hand. `mesh_enqueue_task` is the fallback for a genuinely terminal single step, and it takes an `orchestration_decision` stating which `single_reason` applies.\n- **Base nodes are reserved for environment-specific testing.** Apply Workflow 3.b0: only work that verifies a machine's physical environment runs on a base node (pinned with `required_tags`/`target_node_id`); every ordinary `code_change` gets its own cloned worktree. Node availability is not branch isolation.\n- **Worktree affinity.** Apply Workflow 3.b1: route a branch's follow-ups back to its own worktree node (`required_tags: [\"worktree=<branch>\"]` or `target_node_id`, taken from the `mesh_clone_node` result or a live `mesh_status`); only `convergence` (merge/push) runs base-side.\n- **Classify task difficulty honestly.** Judge each task's real difficulty (`easy`/`medium`/`difficult`/`freeform`) per the Task difficulty section above \u2014 it is a routing hint, and the matched slot's own model/thinking is what launches. Never bend difficulty to chase a model; retune slots instead (`mesh_node_slots_set`).\n- **Retune node profiles when routing is a poor fit \u2014 but only with approval.** A node's capability slots (its provider/model/thinking + difficulty range + capability tags, seen via `mesh_node_slots_list`) are what task\u2192node fitness routing matches against. If you notice a persistent mismatch \u2014 e.g. every `difficult` task lands on a node whose only slot is a cheap model, or a capability a node clearly has isn't declared \u2014 you MAY propose a slot change with `mesh_node_slots_set` (write=false). That returns current-vs-proposed; present that diff to the user with a one-line reason and apply (write=true) ONLY after they approve. It is a WHOLESALE replacement of the node's slots, so include the slots you want to keep. Never rewrite a node's profile silently or without a clear routing reason.\n- **Bootstrap a node's slots from what's actually installed.** When a node has NO slots configured (routing then falls back to \"first available provider\"), or CLI agents were newly installed on it, call `mesh_node_slots_propose({ node_id })` instead of hand-writing a profile. It detects the node's installed CLI agents and drafts a slot list from them \u2014 read-only, it never writes. Present its `proposedSlots` with the `droppedSlots` / `destructive` fields it reports (a wholesale write would delete any existing hand-tuned slot the draft doesn't reproduce, including providers not currently on PATH), then apply with `mesh_node_slots_set({ slots: proposedSlots, write: true })` after approval. It flags `unknownProvider` / `provisional` slots whose placement is a conservative guess rather than an attested one \u2014 call those out rather than presenting them as settled.\n- **When a MAGI panel is unconfigured, bootstrap it from the same detection.** `mesh_magi_review({ task_kind })` resolves its panel SOLELY from the configured kind-panel binding, so a task_kind with no slots fails outright with `magi_kind_not_configured` \u2014 MAGI is simply unavailable until someone binds it, and the usual reason nobody has is that this path is easy to miss. Pass `mesh_node_slots_propose({ node_id, include_magi: true })` to also get a `magiPanel` draft: one slot per DISTINCT detected provider, with no model pinned, because a panel's value is cross-provider independence rather than picking the best provider. It is read-only like the rest of the tool. Apply it per kind with `mesh_magi_kind_panel_set({ task_kind, slots, write: true })` after approval \u2014 and note that write is a WHOLESALE replacement of that kind's binding, so present the dry-run's `currentSlots` first. Do NOT reach for `include_magi` on every slot proposal; it is for when the panels are actually empty or a newly installed provider should join them.\n- **Respect explicit provider requests.** Map: Hermes \u2192 `hermes-cli`, Claude/Claude Code \u2192 `claude-cli`, Codex \u2192 `codex-cli`, Gemini \u2192 `gemini-cli`, Antigravity \u2192 `antigravity-cli`. Never substitute the coordinator's own runtime.\n- **Verify via git, not source.** Use `mesh_git_status` to confirm side effects. Treat agent summaries as self-reports, not verification.\n- **Match concurrency to task kind.** Independent read-only tasks (`live_debug_readonly`) dispatch all at once up to the read-only cap \u2014 no worktree, no free node needed. Each write task needs its OWN branch workspace (Workflow 3.b0); spreading writes across base nodes is NOT a substitute: a mesh with four base nodes still has zero branch isolation. Ramp up cautiously only when tasks share a base branch or submodule pointer (landing order matters). Never launch a second session onto in-flight work for the same issue, even when `mesh_read_chat` shows no final message yet \u2014 successive stages of one investigation stay in their session (see Workflow 3f).\n- **Check history first.** Call `mesh_task_history` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.\n- **Don't reopen already-done work after a resume.** Before reopening a reported issue after context compaction or session resume, check current git state and recent session context. If another session has already completed the work, continue from the existing diff/commit instead of starting a duplicate investigation.\n- **Sequence shared-base-moving merges \u2014 use `mesh_refine_batch` for two or more.** Merging one worktree advances another in-flight worktree's base \u2014 especially a shared submodule pointer \u2014 turning a clean fast-forward into a diverged rebase. When you have 2+ sibling worktrees to land, pass them to `mesh_refine_batch` (dry-run first) instead of calling `mesh_refine_node` once per node: it picks a conflict-aware order (non-submodule first, submodule-touching serialized last), and because each node re-resolves the base and auto-rebases before its own gates, siblings that fall behind are rebased for you rather than by hand. It also avoids the `base_locked` contention that concurrent single-node refines cause. It is not a conflict solver \u2014 a real content or submodule conflict still lands that node in `blocked_review` for manual resolution while the rest of the batch proceeds. Only drop to per-node `mesh_refine_node` for a single branch, or to hand-resolve a node the batch reported blocked.\n- **Converge branches.** After worktree tasks: refine/fast-forward, or classify as `pushed_feature_branch_needs_merge` / `blocked_review` / `cleanup_candidate` / `not_mergeable`. Clean up with `mesh_remove_node`.\n- **Refinery is config-driven.** `mesh_refine_node` must run validation from `.adhdev/refine.{json,yaml,yml}` or `repo-mesh.refine.*`. Heuristics are scaffolding only.\n- **Submodule reachability = publish-needed.** `submodule_reachability_failed` \u2192 classify as `blocked_review`, request user approval to push to submodule main, then rerun `mesh_refine_node`.\n- **Honor per-node instructions.** When a node carries a \u{1F4CC} Node instruction in the nodes section, include the relevant parts of that instruction in the task message you send to that node. Don't paraphrase the instruction into your own words \u2014 quote it verbatim so the worker agent sees exactly what the user wrote.\n- **Mission status does not update itself.** When a mission's tasks are all done or the work is abandoned, explicitly call `mesh_mission_upsert` to set status `completed` or `abandoned`. Never leave a finished mission in `active`. All-cancelled tasks with no further work \u2192 `abandoned`.\n- **Promote durable lessons to operating notes \u2014 especially at mission close.** Before calling `mesh_mission_upsert` with status `completed`/`abandoned`, ask whether this mission taught something a future coordinator needs (a provider quirk, a pattern to avoid, a recovery lesson); if so, call `mesh_record_note` FIRST \u2014 a mission's goal/history is invisible to the next coordinator once it completes, so an unrecorded lesson is lost at exactly the moment it was learned. Record only when all three hold: (a) a coordinator on another day or another session would act differently knowing it, (b) it cannot be rediscovered from code, config, or `git log`, and (c) it is not a one-off detail specific to this single mission. Note that operating notes reach the COORDINATOR prompt only \u2014 they are never injected into delegated worker sessions, so a convention workers must follow belongs in a CI gate or the repo's agent instructions file, not in a note.\n- **Don't spawn a nested coordinator for simple inspection.** Do not spawn a nested coordinator-like agent for simple inspection tasks. If delegation is required, use explicit provider selection and a fully self-contained, bounded task instruction.\n- **Keep internal traffic out of the transcript.** Internal tool calls, status events, control messages, and debug output must not appear as ordinary user-visible chat transcript content unless explicitly marked user-facing by the producing agent.\n- **Never fabricate tool results.** Always call the actual tool.\n- **Keep the user informed.** One or two sentences after each delegation round.\n- **`gated_by` alone does not branch on outcome.** It only waits for the gate to be released \u2014 a task named in `gated_by` dispatches even if you release the gate with `outcome: \"failed\"`. To skip a downstream task on a bad outcome, that task must ALSO declare `run_if` reading `/gate_outcome` (e.g. `{ from: \"<gateRef>\", select: \"/gate_outcome\", op: \"eq\", value: \"passed\" }`). If you cannot express the branch with `run_if`, don't gate it \u2014 investigate and enqueue by hand instead.\n- **Verify a mission goal's claims before dispatching on them.** A mission's goal text is a snapshot from when it was written; \"already investigated\" doesn't mean the file paths, SHAs, or claims it cites are still true today. Before dispatching work that names a specific file/commit/symbol, confirm it still exists with one read-only probe. When the task is a deletion/removal, always add: \"if the target doesn't exist, delete nothing and report that instead.\"\n- **Close missions yourself \u2014 don't wait on passive signals.** `mission_close_candidate` and the idle-active-mission reminder only fire on a genuine idle edge (and the reminder also needs an empty pending-event queue plus a 5-minute debounce), so on a busy day they arrive late or not at all. At the end of every dispatch round \u2014 after a batch lands, after a convergence, before you go idle \u2014 call `mesh_mission_list` yourself and close out anything that's actually done. An `active` mission with no remaining work is a debt, not a state to wait out.\n\n### Task Messaging Requirements\n\nWhen you compose the task message you dispatch to a node, include this requirement so the worker's completion report is verifiable:\n\n- **Branch convergence state.** For a worktree task, require the completion report to classify the touched branch into exactly one final state: `merged_to_main`, `pushed_feature_branch_needs_merge`, `blocked_review`, `cleanup_candidate`, or `not_mergeable`. A task that ends on a non-main branch is not complete unless the report names that state and the next step.\n";
45912
+ DEFAULT_COORDINATOR_RULES = "## Orchestration Workflow\n\n1. **Assess** \u2014 Call `mesh_status` to see which nodes are healthy and available. Check `mesh_task_history` to understand what has already been done in this mesh \u2014 previous delegations, completions, and failures.\n2. **Plan** \u2014 Decompose the user's request into independent tasks for parallel execution, or sequential tasks when dependencies exist. If `mesh_task_history` shows a recent failure for a task, decide whether to retry or reassign. Submit the plan as one `mesh_enqueue_batch` \u2014 a mission is not required to do this. **For multi-task work, a mission is recommended**: call `mesh_mission_upsert` with a title and goal first, then carry that `mission_id` on the batch (a top-level `mission_id` applies to every entry) so the plan survives a coordinator restart and shows up in `mesh_mission_list`. A one-off graph with no need for that survivability/tracking can skip `mission_id` entirely and just call `mesh_enqueue_batch`. Express \"B after A\" ordering with `depends_on` instead of waiting and polling \u2014 the system claims dependents automatically when their dependencies complete. When a mission's outcome is decided, update its status (`completed`/`abandoned`) via `mesh_mission_upsert`. If the prompt already shows an **Active Mission**, continue it from its current task state \u2014 do not re-enqueue tasks that already exist.\n3. **Queue / Delegate** \u2014 The Mesh uses an autonomous pull-based Work Queue:\n a. **Batch-first rule.** Before enqueueing anything, answer ONE question: *when this task reports back, will I read its result and then dispatch more work \u2014 or does the result go to the user with nothing behind it?*\n - **You will act on the result \u2192 `mesh_enqueue_batch`.** If you already intend to read the output and dispatch a next step, that next step is a step you can declare. Declare it: bind the evidence with `inputs_from`, branch on it with `run_if`, or put your own decision behind a coordinator gate and point the follower at it with `gated_by`. \"I need to see the result first\" is HOW a graph edge is expressed, not a reason to skip the graph \u2014 that is exactly what `inputs_from` and gates are for. An investigation you plan to act on is, by this test, a declarable two-step plan.\n - **The result goes to the user and nothing follows \u2192 `mesh_enqueue_task`.** One task, no successor you intend to dispatch yourself. Same-subject continuation of an already-running session is `mesh_send_task`, not a new enqueue either way.\n - Submit the whole materializable plan in ONE batch. A batch may mix worker tasks, `inputs_from` bindings, `run_if` branches, delayed `workspace_ref` preparation, and coordinator gates for approval, Refinery, CI waiting, publish, or deployment. Do not enqueue one known step, wait for it, and then enqueue the next known step by hand.\n - **A mesh with idle nodes or a short plan does not remove this requirement.** \"It is only two steps\" and \"I will just enqueue the next one when this finishes\" are the two ways the graph gets skipped; neither is a reason. Two declarable steps is precisely the case the batch surface exists for.\n - **\"The only thing I am sure of right now is this one task\" is not evidence of a single.** That feeling is about your confidence, not about how many steps are settled; two steps are routinely settled at once while it still feels that way. Count the steps you would actually dispatch. If that count is two or more, it is a batch no matter how the plan feels.\n - **\"I cannot choose the next step until I see this result\" states that the next step EXISTS.** What the result decides is *which branch you take*, not *whether* there is a follow-up \u2014 and a branch that turns on a predecessor's output is what `run_if` selects, what `inputs_from` feeds, and what a coordinator gate holds until you decide. Needing to look at evidence before choosing is the ordinary case for a graph edge, not an exception to it.\n - **A gate or a batch is not overhead when something follows it.** The extra call buys the dispatch of everything behind it, so it costs one call and saves a whole round-trip through you. The only genuinely wasteful gate is one nothing depends on \u2014 a gate no task names in `gated_by` opens nothing when released, which the release response tells you about; that case is answered by declaring the follower, not by abandoning the batch.\n - **If you cancel the tasks behind a gate, close the gate too.** Cancelling a task never touches the gate that was holding it \u2014 gates are a separate control plane on purpose. A gate left over a cancelled branch stays `awaiting_coordinator` forever, and while ANY gate is unsettled its graph can reach no terminal state at all, not even `cancelled`. Close it with `mesh_graph_gate_abandon` and a reason. `mesh_graph_view` flags this case for you as a `gate_abandon` action. Abandon is not a shortcut through a gate: it cancels whatever the gate was holding and yields no gate outcome, so use `mesh_graph_gate_release` whenever you actually want the downstream work to run.\n\n When you do use `mesh_enqueue_task`, pass `orchestration_decision` recording why a single was right (`single_reason`: `only_one_step_known` / `future_step_not_specifiable` / `same_session_continuation` / `legacy_client` / `operator_override`). Omitting it is recorded as `decision_missing`.\n\n Do not invent speculative downstream instructions merely to form a batch \u2014 a step you would not actually dispatch does not belong in the graph. But check the three declaration surfaces before concluding a step is unstatable: `inputs_from` when the instruction is stable and only needs predecessor evidence, a coordinator gate when a human/coordinator decision or external side effect must come first, `run_if` when the branch turns on a structured result predicate. Only when none of the three can state the future step faithfully is single-task enqueue correct.\n\n **The line between the two failures.** The rebuttals above are aimed at declining to declare a step you already intend to dispatch; this guard is aimed at fabricating one you do not. They never both apply to the same step: the test is whether the step exists in your plan, not whether its details are settled. A step you intend to run but whose exact branch depends on evidence is KNOWN and belongs in the batch \u2014 that dependency is what the three surfaces express. A step you would have to make up to fill the batch is UNKNOWN and belongs nowhere. When you genuinely cannot tell which one you are looking at, enqueue the single and record `future_step_not_specifiable`; that is the honest answer and it is measured.\n\n Either way, idle node agents automatically pull tasks from the queue and begin working. A batch inserts atomically (a mid-batch error such as a cycle or bad difficulty rolls the whole set back, so no half-registered chain), and each task's `depends_on` may name sibling tasks by their batch-local `ref` label, forward references included.\n b. **Node Preparation**: Reuse an existing idle session on the correct node/provider before launching a new chat/session. Call `mesh_launch_session` only when no suitable session exists, when the user explicitly asks for a fresh provider/session, or when branch/worktree isolation requires it. **A node is not limited to one live session for read-only work** \u2014 `readonly`/`live_debug_readonly` tasks are exempt from the one-active-per-node invariant, so the SAME node can auto-launch multiple concurrent read-only sessions with no worktree needed. Cloning a worktree costs roughly 10 seconds, so it is cheap enough to create one whenever write work needs a free node; use it for branch isolation, for parallel write tasks (one active write per node), or when a node's read-only queue is deep enough that a second node would clearly finish faster \u2014 call `mesh_clone_node` to create the worktree node first.\n b0. **Base nodes are for environment-specific testing, not for general code changes.** Before dispatching any write task, answer ONE question: *does this task verify the physical environment of a specific machine or OS, or does it only change code?*\n - **Physical-environment task \u2192 base node, targeted.** Pin it with `required_tags` (e.g. `[\"os=win32\"]`) or `target_node_id`. Examples that genuinely require the real machine: verifying a win32 `PATH`/registry/installer layout, a clean-install or uninstall on a specific OS, Homebrew or package-manager state on one particular machine, an OS-dependent runtime behavior (path separators, process spawn, native bindings), or reproducing a bug reported only on that node. A worktree CANNOT substitute for these \u2014 the point is the machine itself.\n - **Everything else (ordinary `code_change`) \u2192 clone a worktree and assign the task there.** Editing source, fixing a bug, adding tests, refactoring, updating docs: none of these care which machine they run on, and all of them need branch isolation. **Do NOT send these to a base node.**\n - **A mesh with several nodes does not remove this requirement.** Node availability and branch isolation are independent concerns: idle base nodes are not a reason to skip cloning, because every base node shares one checkout of the same branch. \"There are 4 nodes free, so I don't need a worktree\" is exactly the wrong inference.\n - **Cloning is nearly free and does NOT cost you an extra dispatch step.** `mesh_clone_node` takes ~10 seconds and returns the new node's `id`/`worktreeBranch`; **auto-launch starts the session on it for you**, so you do not call `mesh_launch_session` \u2014 clone, then enqueue/send against the returned id. Treat it as one extra tool call, never as a reason to fall back to a base node.\n b1. **Keep a branch's work on its worktree (worktree affinity).** This is about routing a branch's follow-ups back to its OWN worktree \u2014 it is never a reason to avoid creating a NEW worktree for independent work. A worktree node is a durable per-branch workspace, not a one-task throwaway \u2014 implement, review, and fix for the same branch all belong on the SAME worktree, and it lives until its work is converged (merged/pushed) and it is cleaned up. So once you clone a worktree for a branch, route every subsequent `code_change`/`validation`/fix task for that branch back to that same node: pass `required_tags: [\"worktree=<branch>\"]` or `target_node_id: <that worktree node's id>`. **Where to get the node id / tag:** the `mesh_clone_node` result returns the new node's `id` and `worktreeBranch` directly \u2014 use them immediately. The Configured Nodes list in this prompt is a launch-time snapshot and will NOT list a worktree you cloned after this session started, so do not rely on it for freshly-cloned worktrees; take the id/branch from the `mesh_clone_node` result, or call `mesh_status` to re-list the live nodes (each worktree there advertises its `worktree=<branch>` tag). Do NOT leave same-branch follow-ups untargeted \u2014 an untargeted task is claimed by whichever node polls first (usually the base machine node), which strands the work off the branch's worktree. The ONE exception is a `convergence` task (merge/push): that is base-only and must NOT be pinned to the worktree.\n c. **Targeted Tasks**: Use `mesh_send_task` only when you need to bypass the queue and force a specific node to execute a task immediately.\n d. For the first dispatch of a new task, provide a **complete, self-contained** instruction that includes all context the agent needs (file paths, line numbers, what to change, why). Do not send partial instructions expecting future follow-up.\n e. For a continuation of the same issue in an existing session, send a concise **delta instruction**: current verified state, the exact failed/blocked step, the newly approved action, and final reporting requirements. Do not resend the full original task or open a new chat solely to continue the same work; that wastes coordinator and worker context.\n f. **Let the investigator apply the fix when the findings settle it \u2014 otherwise split deliberately.** An investigator that has read the source and named the file:line and the fix already holds context a fresh worker must rebuild from scratch, and you would have to restate its findings in the new task message to get there. Task mode is **per task, not per session**: the read-only guardrail is evaluated on each dispatch from that task's own `readonly`/`task_mode`, so you hand off by sending a follow-up `mesh_send_task` to the SAME session WITHOUT the read-only flag (use `task_mode: \"code_change\"`). You do not need a new session or a fresh worktree for the mode to change. **Hand off in-session when** the findings match your hypothesis, the fix stays inside the files just investigated, and no user decision is pending. **Split to a separate task when** the investigation needs a user decision (it surfaced design options, or a cost/risk tradeoff), when it OVERTURNED your hypothesis so the direction itself needs rethinking, or when the fix touches files another in-flight worker owns. **Never convert an investigation whose own conclusion was \"do not change this\"** \u2014 a correct no-op finding is a completed task, and pushing it into a fix produces an unverified change nobody asked for. Dispatching the investigation as an ordinary report-first task skips the handoff, but drops the guardrail against premature fixes \u2014 keep `live_debug_readonly` whenever the point is to find out whether anything is wrong at all.\n4. **Monitor** \u2014 Prefer event-driven completion/status notifications. Do **not** poll `mesh_read_chat` repeatedly. Do **not** repeatedly call `mesh_status` or `mesh_view_queue` just to wait for assigned/generating work. After dispatching a direct or queued task, send one progress update with the task/session handle, then stop. Wait for `pendingCoordinatorEvents` or another completion/approval/status signal, an explicit user status request, or a real timeout/stall signal before reading status/chat/queue again. Use at most one compact `mesh_read_chat` check after a terminal signal. Handle approvals via `mesh_approve`. **Proactively parallelize new work.** When the user reports a new bug or asks for new work, start it immediately if it is independent of in-flight tasks and there is headroom under `maxParallelTasks` \u2014 do not wait for a current task to finish or for the user to prompt you to parallelize. Read-only diagnosis (`live_debug_readonly`) has no isolation or merge cost, so dispatch it in parallel right away. The no-polling / concurrency-limit rules constrain *re-checking or duplicating already-dispatched work*; they are **not** a reason to defer starting a new, independent task.\n - **Arrival order is not occurrence order \u2014 identify every notification by its `taskId`/`sessionId`.** While you are generating, worker notifications are held in the queue and injected together on the tick after you go idle. This is intended (a raw write into a generating session is not consumed as a turn), and the delay is normally well under a minute \u2014 measured median ~1 minute, worst case ~10 minutes. The consequence is that a notification arriving now is **not necessarily about the task you most recently dispatched**. Never infer a notification's subject from timing or from what you just sent: read the `taskId`/`sessionId` in the notification itself and match it to your own record of what you dispatched. Also note that your own coordinator session id appears in these traces, so a session id in a notification is not automatically a worker's. If a notification refers to a task you have already cancelled or completed, treat it as stale \u2014 do not act on it, and say so rather than silently reinterpreting it as being about current work.\n5. **Verify** \u2014 When a task reports completion or git work is visible, call `mesh_git_status` to verify changes were made.\n6. **Checkpoint** \u2014 Call `mesh_checkpoint` to save the work.\n7. **Converge branches** \u2014 Before marking any task complete, classify every touched node/branch into exactly one final state: `merged_to_main`, `pushed_feature_branch_needs_merge`, `blocked_review`, `cleanup_candidate`, or `not_mergeable`. Use `mesh_status` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use `mesh_fast_forward_node` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use `mesh_refine_node` for clean worktree branches when safe \u2014 but when 2+ sibling worktrees share a base, converge them with `mesh_refine_batch` rather than repeated single-node calls (see the sequencing rule in Rules). Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from the configured submodule remote main branch, not merely present on a feature ref or local checkout. If `mesh_refine_node` returns `submodule_reachability_failed` or publish-required evidence, keep the public convergence bucket as `blocked_review`; unless `allowAutoPublishSubmoduleMainCommits` is explicitly enabled and Refinery reports successful non-force publish plus post-publish verification, ask the user for explicit approval to push/publish the unreachable submodule commit(s) to the submodule's default branch, then rerun `mesh_refine_node`. Do not merge the root branch until the submodule commit(s) are reachable from the submodule's default branch. A task that remains off the mesh/repo's own default branch is not fully complete unless the final report names the follow-up state and next step.\n8. **Clean up** \u2014 Remove worktree nodes via `mesh_remove_node` after their work is merged or no longer needed.\n9. **Report** \u2014 Summarize what was done, what changed, any issues, and the branch convergence state.\n\n## Failure Recovery\n\nWhen a node agent stops unexpectedly, the daemon automatically enriches the system message with **Recovery Context** that includes:\n- The number of consecutive failures on that node\n- The original task message (if recorded in the ledger)\n- A recommendation: **retry**, **reassign**, or **escalate**\n\nFollow these recovery rules:\n1. **If \"Retry recommended\"**: Check `mesh_view_queue` first \u2014 the daemon may have auto-requeued. If not, re-launch the session on the same node (`mesh_launch_session`), then resend the original task (`mesh_send_task`). The system message includes the original task text.\n2. **If \"Max retries exceeded\"**: Do NOT retry on the same node. Either reassign the task to a different node, or inform the user that the task requires manual intervention.\n3. **If no recovery context**: The stop may be intentional (normal completion). Use `mesh_read_chat` once to verify, then move on.\n4. **Always record what happened**: After handling a failure, briefly note the outcome in your report to the user.\n5. **Stuck-but-done vs actually-stuck**: If a delegated session appears stuck but has already produced a verified final summary or diff, stop polling noisy tool/terminal transcript bubbles. Verify with `mesh_git_status` or a checkpoint and proceed to landing.\n6. **Refinery falsely blocks a verified-clean branch \u2014 manual fast-forward convergence**: When `mesh_refine_node` falsely blocks a verified-clean branch (stale preflight, or the submodule-gitlink trivial-fast-forward misjudgment), bypass the refine tool and converge by strict fast-forward \u2014 (1) rebase the submodule commit onto the submodule's `origin/<default-branch>`, (2) push the submodule ff-only (verify `git merge-base --is-ancestor` first), (3) rebase the root branch and re-bump the submodule pointer so the root diff stays non-empty, (4) push the root ff-only. NEVER force-push or reset; abort and report on any non-fast-forward.\n\n## Rules\n\n- **Route, don't implement.** Delegate all code reading, analysis, and execution to node agents. Never read source files or run commands in the coordinator \u2014 keep context lean. See also: **Never use local sub-agents** below.\n- **Never use local sub-agents.** Do NOT spawn your runtime's own sub-agents (e.g. Claude Code's Task/Explore/Agent tools, or any equivalent in-process agent-spawning tool) to read code, investigate, run RCA, or implement. Such sub-agents execute on the coordinator's machine, outside the mesh \u2014 they escape mesh parallelism, the ledger/audit trail, node capability profiles, and worktree isolation, and leave no `mesh_task_history` record. ALL code reading, analysis, RCA, and implementation must be delegated through `mesh_enqueue_batch` (the default \u2014 see Workflow 3.a), falling back to `mesh_enqueue_task` only for a terminal single step, to `mesh_send_task` for a same-session continuation (use `task_mode: \"live_debug_readonly\"` for read-only investigation), or cross-verified via `mesh_magi_review` for read-only fan-out. The coordinator's own actions are limited to `mesh_*` tool orchestration and synthesizing results.\n- **Front-load immutable task instructions.** Include everything the agent needs (files, problem, expected fix) in whichever dispatch surface Workflow 3.a selects (`mesh_enqueue_batch` by default; `mesh_enqueue_task` / `mesh_send_task` in their narrower cases). Put predecessor-produced data in explicit `inputs_from` bindings and coordinator decisions in gates; never copy untrusted worker output into a new instruction by hand when a binding can preserve provenance. Append a structured result request at the end: ask the worker to conclude with a JSON block containing `status`, `changedFiles`, `gitStatus`, `validationResults`, `errors`, `nextAction`. The daemon parses this automatically; you can read it from `mesh_task_history`.\n- **Reuse idle sessions.** For follow-up, retry, commit/push, or cleanup on the same issue, send only the delta to the existing idle session. Start a fresh session only when: (a) branch/worktree isolation is required, (b) the existing session had a dispatch failure or provider mismatch, (c) the transcript/runtime is contaminated or interrupted, (d) the user explicitly asks for a different provider/session, or (e) **the delta is a genuinely NEW subject rather than a continuation** \u2014 a new topic appended to an existing session can be dropped or re-run as the previous task, so give it its own task even when a session sits idle. Continuation of the same issue in an already-idle session is allowed and preferred \u2014 this rule blocks concurrent unrelated work interleaved into a live (still-generating) session, not sequential same-issue follow-ups. The test is subject continuity, not timing: carrying an investigation forward into its own fix is the SAME subject and belongs in that session (Workflow 3f), while an unrelated bug is a new subject even if the same session just went idle.\n- **Nodes are separate machines with separate checkouts \u2014 not interchangeable execution slots.** Each node is a different physical computer with its own clone of the repo. Work done on another node must be committed, pushed, and pulled back before this machine sees it, and since RELEASE/DEPLOY runs on the coordinator's own machine, sending a code change elsewhere buys a round trip out and another one back. So **default to this coordinator's own machine for code changes** \u2014 its local node (base or a worktree cloned from it). Routing to a DIFFERENT machine is the exception and needs a reason, of which there are exactly two: (a) **platform-specific verification** that cannot be done here \u2014 win32 PATH/registry, a clean install/uninstall on that OS, that machine's package-manager state; or (b) **parallelizing read-only investigation** across machines. \"That node is idle\" is not a reason. If you catch yourself dispatching a fix to another machine without (a) or (b), route it here instead.\n- **Don't split investigation from the fix.** When a task will plainly end in a code change, dispatch it as `code_change` from the start \u2014 the in-session handoff and split criteria live in Workflow 3f. Split only when the fix genuinely belongs on another machine for reason (a) above; redoing an investigator's context in a fresh session (worse, on another machine) is pure loss.\n- **Batch is the default enqueue surface.** Apply Workflow 3.a: before every enqueue, ask whether you will read this task's result and then dispatch more work. If yes, that successor is declarable \u2014 submit the whole plan as one `mesh_enqueue_batch` with `inputs_from` / `run_if` / `gated_by`, rather than enqueueing a step, waiting, and enqueueing the next by hand. `mesh_enqueue_task` is the fallback for a genuinely terminal single step, and it takes an `orchestration_decision` stating which `single_reason` applies.\n- **Base nodes are reserved for environment-specific testing.** Apply Workflow 3.b0: only work that verifies a machine's physical environment runs on a base node (pinned with `required_tags`/`target_node_id`); every ordinary `code_change` gets its own cloned worktree. Node availability is not branch isolation.\n- **Worktree affinity.** Apply Workflow 3.b1: route a branch's follow-ups back to its own worktree node (`required_tags: [\"worktree=<branch>\"]` or `target_node_id`, taken from the `mesh_clone_node` result or a live `mesh_status`); only `convergence` (merge/push) runs base-side.\n- **Classify task difficulty honestly.** Judge each task's real difficulty (`easy`/`medium`/`difficult`/`freeform`) per the Task difficulty section above \u2014 it is a routing hint, and the matched slot's own model/thinking is what launches. Never bend difficulty to chase a model; retune slots instead (`mesh_node_slots_set`).\n- **Retune node profiles when routing is a poor fit \u2014 but only with approval.** A node's capability slots (its provider/model/thinking + difficulty range + capability tags, seen via `mesh_node_slots_list`) are what task\u2192node fitness routing matches against. If you notice a persistent mismatch \u2014 e.g. every `difficult` task lands on a node whose only slot is a cheap model, or a capability a node clearly has isn't declared \u2014 you MAY propose a slot change with `mesh_node_slots_set` (write=false). That returns current-vs-proposed; present that diff to the user with a one-line reason and apply (write=true) ONLY after they approve. It is a WHOLESALE replacement of the node's slots, so include the slots you want to keep. Never rewrite a node's profile silently or without a clear routing reason.\n- **Bootstrap a node's slots from what's actually installed.** When a node has NO slots configured (routing then falls back to \"first available provider\"), or CLI agents were newly installed on it, call `mesh_node_slots_propose({ node_id })` instead of hand-writing a profile. It detects the node's installed CLI agents and drafts a slot list from them \u2014 read-only, it never writes. Present its `proposedSlots` with the `droppedSlots` / `destructive` fields it reports (a wholesale write would delete any existing hand-tuned slot the draft doesn't reproduce, including providers not currently on PATH), then apply with `mesh_node_slots_set({ slots: proposedSlots, write: true })` after approval. It flags `unknownProvider` / `provisional` slots whose placement is a conservative guess rather than an attested one \u2014 call those out rather than presenting them as settled.\n- **When a MAGI panel is unconfigured, bootstrap it from the same detection.** `mesh_magi_review({ task_kind })` resolves its panel SOLELY from the configured kind-panel binding, so a task_kind with no slots fails outright with `magi_kind_not_configured` \u2014 MAGI is simply unavailable until someone binds it, and the usual reason nobody has is that this path is easy to miss. Pass `mesh_node_slots_propose({ node_id, include_magi: true })` to also get a `magiPanel` draft: one slot per DISTINCT detected provider, with no model pinned, because a panel's value is cross-provider independence rather than picking the best provider. It is read-only like the rest of the tool. Apply it per kind with `mesh_magi_kind_panel_set({ task_kind, slots, write: true })` after approval \u2014 and note that write is a WHOLESALE replacement of that kind's binding, so present the dry-run's `currentSlots` first. Do NOT reach for `include_magi` on every slot proposal; it is for when the panels are actually empty or a newly installed provider should join them.\n- **Respect explicit provider requests.** Map: Hermes \u2192 `hermes-cli`, Claude/Claude Code \u2192 `claude-cli`, Codex \u2192 `codex-cli`, Gemini \u2192 `gemini-cli`, Antigravity \u2192 `antigravity-cli`. Never substitute the coordinator's own runtime.\n- **Verify via git, not source.** Use `mesh_git_status` to confirm side effects. Treat agent summaries as self-reports, not verification.\n- **Match concurrency to task kind.** Independent read-only tasks (`live_debug_readonly`) dispatch all at once up to the read-only cap \u2014 no worktree, no free node needed. Each write task needs its OWN branch workspace (Workflow 3.b0); spreading writes across base nodes is NOT a substitute: a mesh with four base nodes still has zero branch isolation. Ramp up cautiously only when tasks share a base branch or submodule pointer (landing order matters). Never launch a second session onto in-flight work for the same issue, even when `mesh_read_chat` shows no final message yet \u2014 successive stages of one investigation stay in their session (see Workflow 3f).\n- **Check history first.** Call `mesh_task_history` at session start to avoid duplicate work and inform recovery. On failure, read task history before retrying.\n- **Don't reopen already-done work after a resume.** Before reopening a reported issue after context compaction or session resume, check current git state and recent session context. If another session has already completed the work, continue from the existing diff/commit instead of starting a duplicate investigation.\n- **Sequence shared-base-moving merges \u2014 use `mesh_refine_batch` for two or more.** Merging one worktree advances another in-flight worktree's base \u2014 especially a shared submodule pointer \u2014 turning a clean fast-forward into a diverged rebase. When you have 2+ sibling worktrees to land, pass them to `mesh_refine_batch` (dry-run first) instead of calling `mesh_refine_node` once per node: it picks a conflict-aware order (non-submodule first, submodule-touching serialized last), and because each node re-resolves the base and auto-rebases before its own gates, siblings that fall behind are rebased for you rather than by hand. It also avoids the `base_locked` contention that concurrent single-node refines cause. It is not a conflict solver \u2014 a real content or submodule conflict still lands that node in `blocked_review` for manual resolution while the rest of the batch proceeds. Only drop to per-node `mesh_refine_node` for a single branch, or to hand-resolve a node the batch reported blocked.\n- **Converge branches.** After worktree tasks: refine/fast-forward, or classify as `pushed_feature_branch_needs_merge` / `blocked_review` / `cleanup_candidate` / `not_mergeable`. Clean up with `mesh_remove_node`.\n- **Refinery is config-driven.** `mesh_refine_node` must run validation from `.adhdev/refine.{json,yaml,yml}` or `repo-mesh.refine.*`. Heuristics are scaffolding only.\n- **Submodule reachability = publish-needed.** `submodule_reachability_failed` \u2192 classify as `blocked_review`, request user approval to push to submodule main, then rerun `mesh_refine_node`.\n- **Honor per-node instructions.** When a node carries a \u{1F4CC} Node instruction in the nodes section, include the relevant parts of that instruction in the task message you send to that node. Don't paraphrase the instruction into your own words \u2014 quote it verbatim so the worker agent sees exactly what the user wrote.\n- **Mission status does not update itself.** When a mission's tasks are all done or the work is abandoned, explicitly call `mesh_mission_upsert` to set status `completed` or `abandoned`. Never leave a finished mission in `active`. All-cancelled tasks with no further work \u2192 `abandoned`.\n- **Promote durable lessons to operating notes \u2014 especially at mission close.** Before calling `mesh_mission_upsert` with status `completed`/`abandoned`, ask whether this mission taught something a future coordinator needs (a provider quirk, a pattern to avoid, a recovery lesson); if so, call `mesh_record_note` FIRST \u2014 a mission's goal/history is invisible to the next coordinator once it completes, so an unrecorded lesson is lost at exactly the moment it was learned. Record only when all three hold: (a) a coordinator on another day or another session would act differently knowing it, (b) it cannot be rediscovered from code, config, or `git log`, and (c) it is not a one-off detail specific to this single mission. Note that operating notes reach the COORDINATOR prompt only \u2014 they are never injected into delegated worker sessions, so a convention workers must follow belongs in a CI gate or the repo's agent instructions file, not in a note.\n- **Don't spawn a nested coordinator for simple inspection.** Do not spawn a nested coordinator-like agent for simple inspection tasks. If delegation is required, use explicit provider selection and a fully self-contained, bounded task instruction.\n- **Keep internal traffic out of the transcript.** Internal tool calls, status events, control messages, and debug output must not appear as ordinary user-visible chat transcript content unless explicitly marked user-facing by the producing agent.\n- **Never fabricate tool results.** Always call the actual tool.\n- **Keep the user informed.** One or two sentences after each delegation round.\n- **`gated_by` alone does not branch on outcome.** It only waits for the gate to be released \u2014 a task named in `gated_by` dispatches even if you release the gate with `outcome: \"failed\"`. To skip a downstream task on a bad outcome, that task must ALSO declare `run_if` reading `/gate_outcome` (e.g. `{ from: \"<gateRef>\", select: \"/gate_outcome\", op: \"eq\", value: \"passed\" }`). If you cannot express the branch with `run_if`, don't gate it \u2014 investigate and enqueue by hand instead.\n- **Verify a mission goal's claims before dispatching on them.** A mission's goal text is a snapshot from when it was written; \"already investigated\" doesn't mean the file paths, SHAs, or claims it cites are still true today. Before dispatching work that names a specific file/commit/symbol, confirm it still exists with one read-only probe. When the task is a deletion/removal, always add: \"if the target doesn't exist, delete nothing and report that instead.\"\n- **Close missions yourself \u2014 don't wait on passive signals.** `mission_close_candidate` and the idle-active-mission reminder only fire on a genuine idle edge (and the reminder also needs an empty pending-event queue plus a 5-minute debounce), so on a busy day they arrive late or not at all. At the end of every dispatch round \u2014 after a batch lands, after a convergence, before you go idle \u2014 call `mesh_mission_list` yourself and close out anything that's actually done. An `active` mission with no remaining work is a debt, not a state to wait out.\n\n### Task Messaging Requirements\n\nWhen you compose the task message you dispatch to a node, include this requirement so the worker's completion report is verifiable:\n\n- **Branch convergence state.** For a worktree task, require the completion report to classify the touched branch into exactly one final state: `merged_to_main`, `pushed_feature_branch_needs_merge`, `blocked_review`, `cleanup_candidate`, or `not_mergeable`. A task that ends on a non-main branch is not complete unless the report names that state and the next step.\n";
45913
45913
  }
45914
45914
  });
45915
45915
  function resolveCoordinatorRules(meshBaseWorkspace) {
@@ -51628,11 +51628,11 @@ The instruction it carried was never delivered to anyone. If it still matters, r
51628
51628
  }
51629
51629
  });
51630
51630
  function assertTopic(topic) {
51631
- if (typeof topic !== "string" || !TOPIC_RE.test(topic))
51631
+ if (typeof topic !== "string" || !TOPIC_RE.test(topic) || topic === RESERVED_NAME)
51632
51632
  throw new SeqscribeError("ERR_ENTRY_ENCODING", `invalid topic: ${String(topic)}`);
51633
51633
  }
51634
51634
  function assertWriter(writer) {
51635
- if (typeof writer !== "string" || !WRITER_RE.test(writer))
51635
+ if (typeof writer !== "string" || !WRITER_RE.test(writer) || writer === RESERVED_NAME)
51636
51636
  throw new SeqscribeError("ERR_ENTRY_ENCODING", `invalid writer: ${String(writer)}`);
51637
51637
  }
51638
51638
  function assertNonNegSafeInt(n, name) {
@@ -51722,6 +51722,7 @@ The instruction it carried was never delivered to anyone. If it still matters, r
51722
51722
  var WRITER_RE;
51723
51723
  var MAX_KEY_BYTES;
51724
51724
  var CHAIN_RE;
51725
+ var RESERVED_NAME;
51725
51726
  var MAX_NUMERIC_FIELD;
51726
51727
  var init_codec = __esm2({
51727
51728
  "../../vendor/seqscribe/dist/codec.js"() {
@@ -51733,6 +51734,7 @@ The instruction it carried was never delivered to anyone. If it still matters, r
51733
51734
  WRITER_RE = /^[A-Za-z0-9_.:-]{1,128}$/;
51734
51735
  MAX_KEY_BYTES = 512;
51735
51736
  CHAIN_RE = /^[0-9a-f]{64}$/;
51737
+ RESERVED_NAME = "__proto__";
51736
51738
  MAX_NUMERIC_FIELD = Number.MAX_SAFE_INTEGER;
51737
51739
  }
51738
51740
  });
@@ -57927,6 +57929,9 @@ CREATE TABLE IF NOT EXISTS sq_archive (
57927
57929
  retention: { mode: "full" },
57928
57930
  replication: "full-sync",
57929
57931
  access: "content",
57932
+ // P27 derives pre-write hints directly from the register fold. Hash
57933
+ // mode keeps authored setting keys off the Beacon board.
57934
+ hintKeys: "hash",
57930
57935
  finalityAuthority: ADHDEV_AUTHORITY_ID,
57931
57936
  conflict: {
57932
57937
  default: "lww",
@@ -58108,6 +58113,22 @@ CREATE TABLE IF NOT EXISTS sq_archive (
58108
58113
  counters.extraInShadow += extra;
58109
58114
  counters.fieldMismatch += field2;
58110
58115
  counters.mismatches += result.mismatches.length;
58116
+ counters.persistentMismatches += extra + field2;
58117
+ const previouslyMissing = pendingMissing.get(meshId) ?? /* @__PURE__ */ new Set();
58118
+ const stillMissing = /* @__PURE__ */ new Set();
58119
+ for (const m of result.mismatches) {
58120
+ if (m.kind !== "missing_in_shadow") continue;
58121
+ stillMissing.add(m.id);
58122
+ if (previouslyMissing.has(m.id)) {
58123
+ counters.persistentMismatches++;
58124
+ LOG2.warn(
58125
+ "Seqscribe",
58126
+ `parity mismatch PERSISTED across sweeps mesh=${meshId} entry=${shortId(m.id)} ledgerKind=${m.ledgerKind ?? "unknown"} \u2014 the backfill did not repair it; the mesh read cutover stays on the ledger`
58127
+ );
58128
+ }
58129
+ }
58130
+ if (stillMissing.size > 0) pendingMissing.set(meshId, stillMissing);
58131
+ else pendingMissing.delete(meshId);
58111
58132
  if (result.mismatches.length > 0) {
58112
58133
  for (const m of result.mismatches.slice(0, MISMATCH_LOG_CAP)) {
58113
58134
  LOG2.info(
@@ -58133,10 +58154,13 @@ CREATE TABLE IF NOT EXISTS sq_archive (
58133
58154
  counters.extraInShadow = 0;
58134
58155
  counters.fieldMismatch = 0;
58135
58156
  counters.mismatches = 0;
58157
+ counters.persistentMismatches = 0;
58136
58158
  counters.runs = 0;
58159
+ pendingMissing.clear();
58137
58160
  }
58138
58161
  var PARITY_CONSUMER;
58139
58162
  var counters;
58163
+ var pendingMissing;
58140
58164
  var SHADOW_SCAN_PAGE;
58141
58165
  var SHADOW_SCAN_MAX_PAGES;
58142
58166
  var MISMATCH_LOG_CAP;
@@ -58153,8 +58177,10 @@ CREATE TABLE IF NOT EXISTS sq_archive (
58153
58177
  extraInShadow: 0,
58154
58178
  fieldMismatch: 0,
58155
58179
  mismatches: 0,
58180
+ persistentMismatches: 0,
58156
58181
  runs: 0
58157
58182
  };
58183
+ pendingMissing = /* @__PURE__ */ new Map();
58158
58184
  SHADOW_SCAN_PAGE = 500;
58159
58185
  SHADOW_SCAN_MAX_PAGES = 200;
58160
58186
  MISMATCH_LOG_CAP = 20;
@@ -58462,12 +58488,12 @@ CREATE TABLE IF NOT EXISTS sq_archive (
58462
58488
  });
58463
58489
  function resolveMeshDualWriteMode(env2 = process.env) {
58464
58490
  const raw = env2[MESH_DUAL_WRITE_ENV]?.trim().toLowerCase();
58465
- if (!raw) return "shadow";
58491
+ if (!raw) return "primary";
58466
58492
  if (raw === "off") return "off";
58467
58493
  if (raw === "shadow") return "shadow";
58468
58494
  if (raw === "primary") return "primary";
58469
58495
  warnOnce2(
58470
- `unrecognized ${MESH_DUAL_WRITE_ENV}=${raw}; treating as 'shadow' (reads stay on the ledger). Valid values are 'shadow' (default), 'primary' and 'off'.`
58496
+ `unrecognized ${MESH_DUAL_WRITE_ENV}=${raw}; treating as 'shadow' (reads stay on the ledger). Valid values are 'primary' (the default when unset), 'shadow' and 'off'.`
58471
58497
  );
58472
58498
  return "shadow";
58473
58499
  }
@@ -58759,7 +58785,9 @@ CREATE TABLE IF NOT EXISTS sq_archive (
58759
58785
  if (!isConsumerCaughtUp(node, topic, meshId, consumerName)) {
58760
58786
  return { ready: false, reason: "consumer_lag" };
58761
58787
  }
58762
- if (meshParityCounters().mismatches > 0) return { ready: false, reason: "parity_mismatch" };
58788
+ if (meshParityCounters().persistentMismatches > 0) {
58789
+ return { ready: false, reason: "parity_mismatch" };
58790
+ }
58763
58791
  return { ready: true, reason: null };
58764
58792
  }
58765
58793
  function isMeshReadModelReady(meshId) {
@@ -68560,7 +68588,7 @@ When the user asks to **set up / configure / onboard** this repo for Repo Mesh (
68560
68588
  allowAutoPublishSubmoduleMainCommits: {
68561
68589
  type: "boolean",
68562
68590
  default: false,
68563
- description: "When true, Refinery may non-force publish submodule gitlink commits referenced by the refined root tree to each submodule origin/main after validation and patch-equivalence pass, then verify reachability."
68591
+ description: "When true, Refinery may non-force publish submodule gitlink commits referenced by the refined root tree to each submodule's configured default branch after validation and patch-equivalence pass, then verify reachability."
68564
68592
  },
68565
68593
  validation: {
68566
68594
  type: "object",
@@ -69149,261 +69177,945 @@ When the user asks to **set up / configure / onboard** this repo for Repo Mesh (
69149
69177
  OUTPUT_SUMMARY_CHARS = 2e3;
69150
69178
  }
69151
69179
  });
69152
- var mesh_coordinator_exports = {};
69153
- __export2(mesh_coordinator_exports, {
69154
- applyMeshCoordinatorSystemPromptInjection: () => applyMeshCoordinatorSystemPromptInjection,
69155
- buildMeshCoordinatorRegistrationPlan: () => buildMeshCoordinatorRegistrationPlan,
69156
- cleanupCoordinatorAgentFile: () => cleanupCoordinatorAgentFile,
69157
- createHermesManualMeshCoordinatorSetup: () => createHermesManualMeshCoordinatorSetup,
69158
- execUnderPty: () => execUnderPty,
69159
- inspectMeshCoordinatorMcpServerPaths: () => inspectMeshCoordinatorMcpServerPaths,
69160
- resolveMeshCoordinatorSetup: () => resolveMeshCoordinatorSetup,
69161
- stripCoordinatorWrapperFile: () => stripCoordinatorWrapperFile
69162
- });
69163
- function isHermesProvider(provider, cliType) {
69164
- const type2 = cliType?.trim() || provider?.type?.trim() || "";
69165
- return type2 === HERMES_CLI_TYPE;
69180
+ function writeConfigFile(workspace, relativePath, config2) {
69181
+ const target = (0, import_path11.join)(workspace, relativePath);
69182
+ (0, import_fs13.mkdirSync)((0, import_path11.dirname)(target), { recursive: true });
69183
+ (0, import_fs13.writeFileSync)(target, `${JSON.stringify(config2, null, 2)}
69184
+ `, "utf-8");
69185
+ return target;
69166
69186
  }
69167
- function resolveHermesMeshCoordinatorSetup(options) {
69168
- const mcpServer = resolveAdhdevMcpServerLaunch({
69169
- meshId: options.meshId,
69170
- adhdevMcpCommand: options.adhdevMcpCommand,
69171
- adhdevMcpEntryPath: options.adhdevMcpEntryPath,
69172
- nodeExecutable: options.nodeExecutable,
69173
- adhdevMcpTransport: options.adhdevMcpTransport,
69174
- adhdevMcpPort: options.adhdevMcpPort
69175
- });
69176
- if (!mcpServer) {
69177
- return {
69178
- kind: "unsupported",
69179
- reason: "Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode"
69180
- };
69187
+ function suggestMeshWorktreeBootstrapConfig(workspace) {
69188
+ const commands = [];
69189
+ const hasPackageJson = (0, import_fs13.existsSync)((0, import_path11.join)(workspace, "package.json"));
69190
+ const hasNpmLock = (0, import_fs13.existsSync)((0, import_path11.join)(workspace, "package-lock.json"));
69191
+ if (hasPackageJson) {
69192
+ commands.push(
69193
+ hasNpmLock ? { command: "npm", args: ["ci"] } : { command: "npm", args: ["install"] }
69194
+ );
69181
69195
  }
69182
- const configPath = (0, import_node_path3.join)(resolveHermesCoordinatorHome(options.meshId, options.workspace), "config.yaml");
69183
- if (!configPath.trim()) {
69184
- return createHermesManualMeshCoordinatorSetup(options.meshId, options.workspace);
69196
+ const staleInputs = CANDIDATE_STALE_INPUTS.filter((relative8) => (0, import_fs13.existsSync)((0, import_path11.join)(workspace, relative8)));
69197
+ if (!commands.length) {
69198
+ return { commands, staleInputs };
69185
69199
  }
69186
69200
  return {
69187
- kind: "auto_import",
69188
- serverName: DEFAULT_SERVER_NAME,
69189
- configPath,
69190
- configFormat: "hermes_config_yaml",
69191
- mcpServer
69201
+ commands,
69202
+ staleInputs,
69203
+ suggestedConfig: {
69204
+ version: 1,
69205
+ enabled: true,
69206
+ runOnClone: true,
69207
+ required: false,
69208
+ commands,
69209
+ ...staleInputs.length ? { staleInputs } : {}
69210
+ }
69192
69211
  };
69193
69212
  }
69194
- function createHermesManualMeshCoordinatorSetup(meshId, workspace) {
69213
+ function suggestNodeProviderPriority(detected) {
69214
+ const installed = detected.filter((cli) => cli.installed);
69215
+ const installedIds = installed.map((cli) => cli.id);
69216
+ const preferred = PROVIDER_PRIORITY_PREFERENCE.filter((id) => installedIds.includes(id));
69217
+ const rest = installedIds.filter((id) => !preferred.includes(id));
69218
+ const providerPriority = [...preferred, ...rest];
69195
69219
  return {
69196
- kind: "manual",
69197
- serverName: DEFAULT_SERVER_NAME,
69198
- configFormat: "hermes_config_yaml",
69199
- configPathCommand: HERMES_MCP_CONFIG_PATH,
69200
- requiresRestart: true,
69201
- instructions: "Hermes CLI does not auto-import repo-local .mcp.json. Add this MCP server to Hermes config under mcp_servers, then start a fresh Hermes session.",
69202
- template: renderMeshCoordinatorTemplate(
69203
- "mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - mcp\n - --mode\n - ipc\n - --repo-mesh\n - {{meshId}}\n enabled: true\n",
69204
- {
69205
- meshId,
69206
- workspace,
69207
- serverName: DEFAULT_SERVER_NAME,
69208
- adhdevMcpCommand: DEFAULT_ADHDEV_MCP_COMMAND
69209
- }
69210
- )
69220
+ providerPriority,
69221
+ installedProviders: installed.map((cli) => ({
69222
+ id: cli.id,
69223
+ displayName: cli.displayName,
69224
+ ...cli.version ? { version: cli.version } : {}
69225
+ }))
69211
69226
  };
69212
69227
  }
69213
- function resolveMeshCoordinatorSetup(options) {
69214
- const { provider, meshId, workspace } = options;
69215
- const config2 = provider?.meshCoordinator;
69216
- if (!config2?.supported) {
69217
- return {
69218
- kind: "unsupported",
69219
- reason: config2?.reason || "Provider does not declare Repo Mesh coordinator support"
69220
- };
69228
+ function runMeshInit(mesh, workspace, detected, options = {}) {
69229
+ const write = options.write === true;
69230
+ const overwrite = options.overwrite === true;
69231
+ const refine2 = applyConfigSuggestion({
69232
+ workspace,
69233
+ relativePath: MESH_INIT_REFINE_CONFIG_PATH,
69234
+ existing: loadMeshRefineConfig(mesh, workspace).config,
69235
+ suggestedConfig: suggestMeshRefineConfig(mesh, workspace).suggestedConfig,
69236
+ validate: (config2) => validateMeshRefineConfig(config2, MESH_INIT_REFINE_CONFIG_PATH).valid,
69237
+ write,
69238
+ overwrite
69239
+ });
69240
+ const bootstrapSuggestion = suggestMeshWorktreeBootstrapConfig(workspace);
69241
+ const worktreeBootstrap = applyConfigSuggestion({
69242
+ workspace,
69243
+ relativePath: MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH,
69244
+ existing: loadMeshWorktreeBootstrapConfig(mesh, workspace).config,
69245
+ suggestedConfig: bootstrapSuggestion.suggestedConfig,
69246
+ validate: (config2) => validateMeshWorktreeBootstrapConfig(config2, MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH).valid,
69247
+ write,
69248
+ overwrite
69249
+ });
69250
+ const changeImpactLoaded = loadChangeImpactConfig(workspace);
69251
+ const changeImpact = applyConfigSuggestion({
69252
+ workspace,
69253
+ relativePath: MESH_INIT_CHANGE_IMPACT_CONFIG_PATH,
69254
+ existing: changeImpactLoaded.sourceType === "repo_file" ? changeImpactLoaded.config : void 0,
69255
+ suggestedConfig: suggestChangeImpactConfig(workspace).suggestedConfig,
69256
+ validate: (config2) => validateChangeImpactConfig(config2, MESH_INIT_CHANGE_IMPACT_CONFIG_PATH).valid,
69257
+ write,
69258
+ overwrite
69259
+ });
69260
+ const providers = suggestNodeProviderPriority(detected);
69261
+ const refineLoaded = loadMeshRefineConfig(mesh, workspace);
69262
+ const bootstrapLoaded = loadMeshWorktreeBootstrapConfig(mesh, workspace);
69263
+ let magiKindPanels = {};
69264
+ try {
69265
+ magiKindPanels = listMagiKindPanelsReadOnly(typeof mesh?.id === "string" ? mesh.id : void 0);
69266
+ } catch {
69267
+ magiKindPanels = {};
69221
69268
  }
69222
- if (isHermesProvider(provider, options.cliType)) {
69223
- return resolveHermesMeshCoordinatorSetup(options);
69269
+ const currentConfig2 = {
69270
+ refine: refineLoaded.config,
69271
+ worktreeBootstrap: bootstrapLoaded.config,
69272
+ changeImpact: changeImpactLoaded.sourceType === "repo_file" ? changeImpactLoaded.config : void 0,
69273
+ sourceTypes: {
69274
+ refine: refineLoaded.sourceType,
69275
+ worktreeBootstrap: bootstrapLoaded.sourceType,
69276
+ changeImpact: changeImpactLoaded.sourceType
69277
+ },
69278
+ magiKindPanels
69279
+ };
69280
+ return {
69281
+ success: true,
69282
+ workspace,
69283
+ dryRun: !write,
69284
+ refine: refine2,
69285
+ worktreeBootstrap,
69286
+ changeImpact,
69287
+ providers,
69288
+ currentConfig: currentConfig2,
69289
+ note: write ? "Configs written to disk are the execution source of truth; suggestions are scaffold and only take effect once saved. providerPriority is a recommendation \u2014 apply it to node policy via clone/policy update. currentConfig echoes what was on disk before this run." : "Dry-run: no files written. Re-run with write=true to persist the suggested configs. Heuristic suggestions never execute until saved as repo config. Use currentConfig to diff current-vs-suggested before overwriting."
69290
+ };
69291
+ }
69292
+ function applyConfigSuggestion(input) {
69293
+ const { workspace, relativePath, existing, suggestedConfig, validate, write, overwrite } = input;
69294
+ const absolute = (0, import_path11.join)(workspace, relativePath);
69295
+ if (existing !== void 0 && !overwrite) {
69296
+ return { path: absolute, relativePath, written: false, skippedReason: "already_exists", config: existing };
69224
69297
  }
69225
- const mcpConfig = config2.mcpConfig;
69226
- if (!mcpConfig || mcpConfig.mode === "none") {
69227
- return {
69228
- kind: "unsupported",
69229
- reason: config2.reason || "Provider does not declare a usable Repo Mesh MCP configuration mode"
69230
- };
69298
+ if (!suggestedConfig || !validate(suggestedConfig)) {
69299
+ return { path: absolute, relativePath, written: false, skippedReason: "no_suggestion" };
69231
69300
  }
69232
- const serverName = mcpConfig.serverName?.trim() || DEFAULT_SERVER_NAME;
69233
- if (mcpConfig.mode === "auto_import") {
69234
- const path63 = mcpConfig.path?.trim();
69235
- if (!path63) {
69236
- return { kind: "unsupported", reason: "Provider auto-import MCP config is missing a config path" };
69237
- }
69238
- const mcpServer = resolveAdhdevMcpServerLaunch({
69239
- meshId,
69240
- adhdevMcpCommand: options.adhdevMcpCommand,
69241
- adhdevMcpEntryPath: options.adhdevMcpEntryPath,
69242
- nodeExecutable: options.nodeExecutable,
69243
- adhdevMcpTransport: options.adhdevMcpTransport,
69244
- adhdevMcpPort: options.adhdevMcpPort
69245
- });
69246
- if (!mcpServer) {
69247
- return {
69248
- kind: "unsupported",
69249
- reason: "Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode"
69250
- };
69251
- }
69252
- return {
69253
- kind: "auto_import",
69254
- serverName,
69255
- configPath: resolveMcpConfigPath(path63, workspace),
69256
- configFormat: mcpConfig.format,
69257
- mcpServer
69258
- };
69301
+ if (!write) {
69302
+ return { path: absolute, relativePath, written: false, config: suggestedConfig };
69259
69303
  }
69260
- if (mcpConfig.mode === "manual") {
69261
- const instructions = mcpConfig.instructions?.trim();
69262
- const template = mcpConfig.template;
69263
- if (!instructions || !template?.trim()) {
69264
- return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
69265
- }
69266
- const mcpServer = resolveAdhdevMcpServerLaunch({
69267
- meshId,
69268
- adhdevMcpCommand: options.adhdevMcpCommand,
69269
- adhdevMcpEntryPath: options.adhdevMcpEntryPath,
69270
- nodeExecutable: options.nodeExecutable,
69271
- adhdevMcpTransport: options.adhdevMcpTransport,
69272
- adhdevMcpPort: options.adhdevMcpPort
69304
+ const writtenPath = writeConfigFile(workspace, relativePath, suggestedConfig);
69305
+ return { path: writtenPath, relativePath, written: true, config: suggestedConfig };
69306
+ }
69307
+ var import_fs13;
69308
+ var import_path11;
69309
+ var MESH_INIT_REFINE_CONFIG_PATH;
69310
+ var MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH;
69311
+ var MESH_INIT_CHANGE_IMPACT_CONFIG_PATH;
69312
+ var CANDIDATE_STALE_INPUTS;
69313
+ var PROVIDER_PRIORITY_PREFERENCE;
69314
+ var init_mesh_init = __esm2({
69315
+ "src/mesh/mesh-init.ts"() {
69316
+ "use strict";
69317
+ import_fs13 = require("fs");
69318
+ import_path11 = require("path");
69319
+ init_refine_config();
69320
+ init_worktree_bootstrap_config();
69321
+ init_change_impact_config();
69322
+ init_mesh_config();
69323
+ MESH_INIT_REFINE_CONFIG_PATH = MESH_REFINE_CONFIG_LOCATIONS[0];
69324
+ MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH = MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS[0];
69325
+ MESH_INIT_CHANGE_IMPACT_CONFIG_PATH = CHANGE_IMPACT_CONFIG_LOCATIONS[0];
69326
+ CANDIDATE_STALE_INPUTS = [
69327
+ "package-lock.json",
69328
+ "pnpm-lock.yaml",
69329
+ "yarn.lock",
69330
+ "bun.lockb",
69331
+ "Cargo.lock",
69332
+ "go.sum",
69333
+ "poetry.lock",
69334
+ "requirements.txt"
69335
+ ];
69336
+ PROVIDER_PRIORITY_PREFERENCE = ["claude-cli", "codex-cli", "gemini-cli"];
69337
+ }
69338
+ });
69339
+ var mesh_onboarding_plan_exports = {};
69340
+ __export2(mesh_onboarding_plan_exports, {
69341
+ planMeshOnboarding: () => planMeshOnboarding,
69342
+ resolveRootDefaultBranch: () => resolveRootDefaultBranch
69343
+ });
69344
+ async function git(cwd, args, allowFailure = false) {
69345
+ try {
69346
+ const { stdout } = await execFileAsync3("git", args, {
69347
+ cwd,
69348
+ encoding: "utf8",
69349
+ timeout: GIT_TIMEOUT_MS2,
69350
+ maxBuffer: 4 * 1024 * 1024,
69351
+ windowsHide: true,
69352
+ env: gitChildEnv()
69273
69353
  });
69274
- if (!mcpServer) {
69275
- return {
69276
- kind: "unsupported",
69277
- reason: "Could not resolve the ADHDev MCP server entrypoint and transport arguments"
69278
- };
69354
+ return (stdout || "").trim();
69355
+ } catch (error48) {
69356
+ if (allowFailure) return "";
69357
+ const stderr = typeof error48?.stderr === "string" ? error48.stderr.trim() : "";
69358
+ throw new Error(stderr || error48?.message || `git ${args[0]} failed`);
69359
+ }
69360
+ }
69361
+ async function canonicalPath(path63) {
69362
+ const absolute = (0, import_node_path2.resolve)(path63);
69363
+ try {
69364
+ return await (0, import_promises5.realpath)(absolute);
69365
+ } catch {
69366
+ return absolute;
69367
+ }
69368
+ }
69369
+ function pathInside(child, parent) {
69370
+ const rel = (0, import_node_path2.relative)(parent, child);
69371
+ return rel !== "" && rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !(0, import_node_path2.isAbsolute)(rel);
69372
+ }
69373
+ function failure2(code, error48, action, extra = {}) {
69374
+ return { success: false, dryRun: true, code, error: error48, action, ...extra };
69375
+ }
69376
+ function isPlanFailure(value) {
69377
+ return value.success === false;
69378
+ }
69379
+ function parseStatus(raw) {
69380
+ const lines = raw.split(/\r?\n/).filter((line) => line && !line.startsWith("# "));
69381
+ const conflictFiles = [];
69382
+ for (const line of lines) {
69383
+ if (line.startsWith("u ")) {
69384
+ const fields = line.split(" ");
69385
+ conflictFiles.push(fields.slice(10).join(" ") || fields.at(-1) || "(unknown)");
69386
+ continue;
69279
69387
  }
69280
- let renderedTemplate = renderMeshCoordinatorTemplate(template, {
69281
- meshId,
69282
- workspace,
69283
- serverName,
69284
- adhdevMcpCommand: mcpServer.command,
69285
- adhdevMcpArgs: mcpServer.args.join(" ")
69286
- });
69287
- const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
69288
- if (isCliCommand) {
69289
- if (!/\{\{\s*adhdevMcpArgs\s*\}\}/.test(template)) {
69290
- renderedTemplate = replaceLegacyCliCommandMcpArgs(renderedTemplate, mcpServer.args);
69388
+ if (line.startsWith("1 ") || line.startsWith("2 ")) {
69389
+ const xy = line.slice(2, 4);
69390
+ if (["DD", "AU", "UD", "UA", "DU", "AA", "UU"].includes(xy)) {
69391
+ conflictFiles.push(line.split(" ").at(-1) || "(unknown)");
69291
69392
  }
69292
- return {
69293
- kind: "cli_command",
69294
- serverName,
69295
- command: renderedTemplate.trim(),
69296
- requiresRestart: mcpConfig.requiresRestart === true,
69297
- instructions,
69298
- mcpServer
69299
- };
69300
69393
  }
69301
- return {
69302
- kind: "manual",
69303
- serverName,
69304
- configFormat: mcpConfig.format,
69305
- configPathCommand: mcpConfig.configPathCommand,
69306
- requiresRestart: mcpConfig.requiresRestart === true,
69307
- instructions,
69308
- template: renderedTemplate
69309
- };
69310
69394
  }
69311
69395
  return {
69312
- kind: "unsupported",
69313
- reason: `Unsupported Repo Mesh MCP configuration mode: ${String(mcpConfig.mode)}`
69396
+ dirty: lines.length > 0,
69397
+ changedFileCount: lines.length,
69398
+ hasConflicts: conflictFiles.length > 0,
69399
+ conflictFiles
69314
69400
  };
69315
69401
  }
69316
- function renderMeshCoordinatorTemplate(template, values) {
69317
- return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand|adhdevMcpArgs)\s*\}\}/g, (_, key2) => values[key2] || "");
69402
+ async function discoverRemotes(repoRoot, currentBranch) {
69403
+ const names = (await git(repoRoot, ["remote"], true)).split(/\r?\n/).map((v) => v.trim()).filter(Boolean);
69404
+ const remotes = [];
69405
+ for (const name of names) {
69406
+ const urls = (await git(repoRoot, ["remote", "get-url", "--all", name], true)).split(/\r?\n/).map((v) => v.trim()).filter(Boolean);
69407
+ const identities2 = [...new Set(urls.map(normalizeRepoIdentity).filter(Boolean))];
69408
+ remotes.push({ name, urls, identities: identities2 });
69409
+ }
69410
+ const origin = remotes.find((remote) => remote.name === "origin");
69411
+ const trackingRemoteName = await git(repoRoot, ["config", "--get", `branch.${currentBranch}.remote`], true);
69412
+ const upstream = remotes.find((remote) => remote.name === trackingRemoteName) || remotes.find((remote) => remote.name === "upstream");
69413
+ const identities = [...new Set(remotes.flatMap((remote) => remote.identities))];
69414
+ const selectedRemote = origin || upstream || (remotes.length === 1 ? remotes[0] : void 0);
69415
+ return {
69416
+ remotes,
69417
+ origin,
69418
+ upstream,
69419
+ selectedRemote,
69420
+ ambiguous: identities.length > 1 || !!remotes.find((remote) => remote.identities.length > 1)
69421
+ };
69318
69422
  }
69319
- function replaceLegacyCliCommandMcpArgs(command, args) {
69320
- return command.replace(
69321
- /\bmcp\s+--mode\s+(?:ipc|local)\s+--repo-mesh\s+\S+(?:\s+--port\s+\d+)?\s*$/,
69322
- args.join(" ")
69323
- );
69423
+ async function deriveLocalRepoIdentity(repoRoot) {
69424
+ const raw = await git(repoRoot, ["rev-list", "--max-parents=0", "HEAD"], true);
69425
+ const roots = raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line)).sort();
69426
+ const root = roots[0];
69427
+ return root ? `local/${root.toLowerCase()}` : "";
69324
69428
  }
69325
- function resolveHermesCoordinatorHome(meshId, workspace) {
69326
- const key2 = `${meshId || "mesh"}
69327
- ${(0, import_node_path3.resolve)(workspace || os13.tmpdir())}`;
69328
- const hash2 = shortHash(key2);
69329
- return (0, import_node_path3.join)(os13.tmpdir(), `adhdev-hermes-mesh-coordinator-${hash2}`);
69429
+ async function resolveDefaultBranch(repoRoot, remoteName, currentBranch) {
69430
+ const symbolic = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", `refs/remotes/${remoteName}/HEAD`], true);
69431
+ if (symbolic.startsWith(`${remoteName}/`)) return symbolic.slice(remoteName.length + 1);
69432
+ for (const candidate of ["main", "master"]) {
69433
+ const exists = await git(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/remotes/${remoteName}/${candidate}`], true) || await git(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/heads/${candidate}`], true);
69434
+ if (exists) return candidate;
69435
+ }
69436
+ return currentBranch;
69330
69437
  }
69331
- function resolveMcpConfigPath(configPath, workspace) {
69332
- const trimmed = configPath.trim();
69333
- if (trimmed === "~") return os13.homedir();
69334
- if (trimmed.startsWith("~/")) return (0, import_node_path3.join)(os13.homedir(), trimmed.slice(2));
69335
- if ((0, import_node_path3.isAbsolute)(trimmed)) return trimmed;
69336
- return (0, import_node_path3.join)(workspace, trimmed);
69438
+ async function resolveRootDefaultBranch(repoRoot, mesh, opts = {}) {
69439
+ const meshDefault = typeof mesh?.defaultBranch === "string" ? mesh.defaultBranch.trim() : "";
69440
+ if (meshDefault) return meshDefault;
69441
+ const remote = opts.remote?.trim() || "origin";
69442
+ const symbolic = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", `refs/remotes/${remote}/HEAD`], true);
69443
+ if (symbolic.startsWith(`${remote}/`)) return symbolic.slice(remote.length + 1);
69444
+ const currentBranch = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", "HEAD"], true);
69445
+ return currentBranch || "main";
69337
69446
  }
69338
- function resolveAdhdevMcpServerLaunch(options) {
69339
- const directEntryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath);
69340
- if (directEntryPath) {
69341
- const transport2 = resolveMcpTransport(options.adhdevMcpTransport);
69342
- const args2 = [directEntryPath, "--mode", transport2, "--repo-mesh", options.meshId];
69343
- const port2 = resolveMcpPort(options.adhdevMcpPort);
69344
- if (port2 !== void 0) args2.push("--port", String(port2));
69345
- return {
69346
- command: resolveNodeExecutable(options.nodeExecutable),
69347
- args: args2
69348
- };
69447
+ async function discoverGit(workspaceInput) {
69448
+ const workspace = await canonicalPath(workspaceInput || process.cwd());
69449
+ const repoRootRaw = await git(workspace, ["rev-parse", "--show-toplevel"], true);
69450
+ if (!repoRootRaw) {
69451
+ return failure2(
69452
+ "not_git_repository",
69453
+ `Not a Git repository: ${workspace}`,
69454
+ "Choose a directory inside a Git checkout, or initialize/clone the repository before planning Repo Mesh onboarding."
69455
+ );
69349
69456
  }
69350
- const command = resolveAdhdevCommand(options.adhdevMcpCommand);
69351
- const transport = resolveMcpTransport(options.adhdevMcpTransport);
69352
- const directMcpEntrypoint = (0, import_node_path3.basename)(command).startsWith("adhdev-mcp") || command.includes("/vendor/mcp-server/") || command.includes("\\vendor\\mcp-server\\");
69353
- const args = [...directMcpEntrypoint ? [] : ["mcp"], "--mode", transport, "--repo-mesh", options.meshId];
69354
- const port = resolveMcpPort(options.adhdevMcpPort);
69355
- if (port !== void 0) args.push("--port", String(port));
69356
- return {
69357
- command,
69358
- args
69457
+ const repoRoot = await canonicalPath(repoRootRaw);
69458
+ const gitDirRaw = await git(repoRoot, ["rev-parse", "--git-dir"]);
69459
+ const commonDirRaw = await git(repoRoot, ["rev-parse", "--git-common-dir"]);
69460
+ const gitDir = await canonicalPath((0, import_node_path2.isAbsolute)(gitDirRaw) ? gitDirRaw : (0, import_node_path2.resolve)(repoRoot, gitDirRaw));
69461
+ const commonDir = await canonicalPath((0, import_node_path2.isAbsolute)(commonDirRaw) ? commonDirRaw : (0, import_node_path2.resolve)(repoRoot, commonDirRaw));
69462
+ const currentBranch = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", "HEAD"], true);
69463
+ const headCommit = await git(repoRoot, ["rev-parse", "HEAD"], true);
69464
+ const worktrees = await listWorktrees(repoRoot);
69465
+ const canonicalWorktrees = await Promise.all(worktrees.map(async (entry) => ({
69466
+ ...entry,
69467
+ path: await canonicalPath(entry.path)
69468
+ })));
69469
+ const mainWorktreeRoot = canonicalWorktrees[0]?.path || repoRoot;
69470
+ const isMainCheckout = repoRoot === mainWorktreeRoot;
69471
+ const isLinkedWorktree = gitDir !== commonDir;
69472
+ const parentRepoRootRaw = await git((0, import_node_path2.dirname)(repoRoot), ["rev-parse", "--show-toplevel"], true);
69473
+ const parentRepoRoot = parentRepoRootRaw ? await canonicalPath(parentRepoRootRaw) : "";
69474
+ const isNestedWorktree = !!parentRepoRoot && parentRepoRoot !== repoRoot && pathInside(repoRoot, parentRepoRoot);
69475
+ const status = parseStatus(await git(repoRoot, ["status", "--porcelain=v2", "--branch", "--untracked-files=normal"]));
69476
+ const partial2 = {
69477
+ inputPath: workspaceInput,
69478
+ workspace,
69479
+ repoRoot,
69480
+ gitDir,
69481
+ commonDir,
69482
+ mainWorktreeRoot,
69483
+ isMainCheckout,
69484
+ isLinkedWorktree,
69485
+ isNestedWorktree,
69486
+ worktrees: canonicalWorktrees,
69487
+ currentBranch,
69488
+ headCommit,
69489
+ ...status
69359
69490
  };
69360
- }
69361
- function resolveAdhdevCommand(explicitCommand) {
69362
- return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND;
69363
- }
69364
- function inspectMeshCoordinatorMcpServerPaths(mcpServer, options = {}) {
69365
- const embeddedPaths = [mcpServer.command, ...mcpServer.args].filter((value) => (0, import_node_path3.isAbsolute)(value));
69366
- if (embeddedPaths.length === 0) {
69367
- return { state: "absent", referencedPath: null, volatile: false, volatileReason: null, warning: null };
69491
+ if (!currentBranch) {
69492
+ return failure2(
69493
+ "detached_head",
69494
+ `Detached HEAD at ${headCommit.slice(0, 12) || "(unknown commit)"}.`,
69495
+ "Check out a named, safe branch before adding or cloning a Repo Mesh node.",
69496
+ { discovery: partial2 }
69497
+ );
69368
69498
  }
69369
- let firstMissing = null;
69370
- let firstVolatile = null;
69371
- for (const embeddedPath of embeddedPaths) {
69372
- const health = inspectEmbeddedPath(embeddedPath, options.env ?? process.env);
69373
- if (health.state === "missing" && !firstMissing) firstMissing = health;
69374
- if (health.volatile && !firstVolatile) firstVolatile = health;
69499
+ const branchValid = await git(repoRoot, ["check-ref-format", "--branch", currentBranch], true);
69500
+ if (!branchValid) {
69501
+ return failure2(
69502
+ "unsafe_branch",
69503
+ `Current branch is not a safe Git branch name: ${currentBranch}`,
69504
+ "Check out or create a valid named branch, then re-run the onboarding plan.",
69505
+ { discovery: partial2 }
69506
+ );
69375
69507
  }
69376
- const serverName = options.serverName?.trim() || "adhdev-mesh";
69377
- const target = options.target?.trim() || `MCP setup for ${serverName}`;
69378
- const propagationNote = options.repoLocal ? " The config file lives inside the workspace and may be committed \u2014 this machine-specific absolute path would propagate to teammates' machines, where it matches nothing." : "";
69379
- if (firstMissing) {
69380
- return {
69381
- state: "missing",
69382
- referencedPath: firstMissing.referencedPath,
69383
- volatile: firstMissing.volatile || Boolean(firstVolatile),
69384
- volatileReason: firstMissing.volatileReason || firstVolatile?.volatileReason || null,
69385
- warning: `${target} embeds ${firstMissing.referencedPath}, which does not exist on this machine \u2014 the ${serverName} MCP server will fail to start (dangling reference, the statusline failure class).${propagationNote}`
69386
- };
69508
+ if (isNestedWorktree) {
69509
+ return failure2(
69510
+ "nested_worktree",
69511
+ `Repository ${repoRoot} is nested inside Git checkout ${parentRepoRoot}.`,
69512
+ "Choose the intended outer checkout or move the nested checkout outside it before Repo Mesh onboarding.",
69513
+ { discovery: partial2 }
69514
+ );
69387
69515
  }
69388
- if (firstVolatile) {
69389
- return {
69390
- state: "ok",
69391
- referencedPath: firstVolatile.referencedPath,
69392
- volatile: true,
69393
- volatileReason: firstVolatile.volatileReason,
69394
- warning: `${target} embeds ${firstVolatile.referencedPath}, which is ${firstVolatile.volatileReason}. It works today, but the ${serverName} entry will dangle once that location is cleaned up \u2014 the same failure class as the 2026-08-20 statusline incident.${propagationNote}`
69395
- };
69516
+ if (status.hasConflicts) {
69517
+ return failure2(
69518
+ "conflicted_workspace",
69519
+ `Workspace has unresolved conflicts (${status.conflictFiles.join(", ")}).`,
69520
+ "Resolve or abort the in-progress Git operation before adding this workspace to Repo Mesh.",
69521
+ { discovery: partial2 }
69522
+ );
69396
69523
  }
69397
- return { state: "ok", referencedPath: embeddedPaths[0], volatile: false, volatileReason: null, warning: null };
69398
- }
69399
- function resolveAdhdevMcpEntryPath(explicitEntryPath) {
69400
- const entryPath = explicitEntryPath?.trim() || process.env.ADHDEV_COORDINATOR_MCP_ENTRY_PATH?.trim();
69401
- return entryPath || null;
69402
- }
69403
- function resolveNodeExecutable(explicitNodeExecutable) {
69404
- return explicitNodeExecutable?.trim() || process.env.ADHDEV_COORDINATOR_NODE_EXECUTABLE?.trim() || process.execPath;
69405
- }
69406
- function resolveMcpTransport(explicitTransport) {
69524
+ const remoteDiscovery = await discoverRemotes(repoRoot, currentBranch);
69525
+ if (!remoteDiscovery.remotes.length) {
69526
+ const localIdentity = await deriveLocalRepoIdentity(repoRoot);
69527
+ if (!localIdentity) {
69528
+ return failure2(
69529
+ "no_commits_for_local_identity",
69530
+ "This repository has no remote and no commits, so a stable repository identity cannot be derived.",
69531
+ "Create at least one commit (the root commit becomes the local identity), or pass an explicit repo identity when creating the mesh.",
69532
+ { discovery: partial2 }
69533
+ );
69534
+ }
69535
+ const localDefaultBranch = await resolveDefaultBranch(repoRoot, "", currentBranch);
69536
+ return {
69537
+ ...partial2,
69538
+ remotes: [],
69539
+ origin: void 0,
69540
+ upstream: void 0,
69541
+ selectedRemote: "",
69542
+ repoIdentity: localIdentity,
69543
+ defaultBranch: localDefaultBranch
69544
+ };
69545
+ }
69546
+ if (!remoteDiscovery.selectedRemote) {
69547
+ const detail = remoteDiscovery.remotes.map((remote) => remote.name).join(", ");
69548
+ return failure2(
69549
+ "remote_not_selected",
69550
+ `No canonical remote could be selected among multiple remotes (${detail}).`,
69551
+ "Name one of them origin or upstream, set the current branch to track one of them, or pass an explicit repo identity when creating the mesh.",
69552
+ { discovery: { ...partial2, remotes: remoteDiscovery.remotes, origin: remoteDiscovery.origin, upstream: remoteDiscovery.upstream } }
69553
+ );
69554
+ }
69555
+ if (remoteDiscovery.ambiguous) {
69556
+ const detail = remoteDiscovery.remotes.map((remote) => `${remote.name}=${remote.identities.join("|") || "(no URL)"}`).join(", ");
69557
+ return failure2(
69558
+ "ambiguous_remotes",
69559
+ `Multiple unrelated remote identities were found: ${detail}`,
69560
+ "Remove the ambiguity or explicitly choose/reconcile the canonical remote before onboarding.",
69561
+ { discovery: { ...partial2, remotes: remoteDiscovery.remotes, origin: remoteDiscovery.origin, upstream: remoteDiscovery.upstream } }
69562
+ );
69563
+ }
69564
+ const repoIdentity = remoteDiscovery.selectedRemote.identities[0];
69565
+ if (!repoIdentity) {
69566
+ return failure2(
69567
+ "remote_not_found",
69568
+ `Remote '${remoteDiscovery.selectedRemote.name}' has no usable URL.`,
69569
+ "Configure a valid fetch URL for the selected remote before onboarding.",
69570
+ { discovery: partial2 }
69571
+ );
69572
+ }
69573
+ const defaultBranch = await resolveDefaultBranch(repoRoot, remoteDiscovery.selectedRemote.name, currentBranch);
69574
+ return {
69575
+ ...partial2,
69576
+ remotes: remoteDiscovery.remotes,
69577
+ origin: remoteDiscovery.origin,
69578
+ upstream: remoteDiscovery.upstream,
69579
+ selectedRemote: remoteDiscovery.selectedRemote.name,
69580
+ repoIdentity,
69581
+ defaultBranch
69582
+ };
69583
+ }
69584
+ async function membershipsFor(discovery, meshes) {
69585
+ const matches = [];
69586
+ for (const mesh of meshes) {
69587
+ const compatible = normalizeRepoIdentity(mesh.repoIdentity) === discovery.repoIdentity;
69588
+ let emittedMesh = false;
69589
+ for (const node of mesh.nodes || []) {
69590
+ const rawWorkspace = (0, import_node_path2.resolve)(node.workspace || "");
69591
+ const canonicalWorkspace = await canonicalPath(rawWorkspace);
69592
+ const canonicalRepoRoot = node.repoRoot ? await canonicalPath(node.repoRoot) : canonicalWorkspace;
69593
+ const exactWorkspace = canonicalWorkspace === discovery.workspace;
69594
+ if (exactWorkspace || canonicalRepoRoot === discovery.repoRoot) {
69595
+ matches.push({
69596
+ meshId: mesh.id,
69597
+ meshName: mesh.name,
69598
+ repoIdentity: normalizeRepoIdentity(mesh.repoIdentity),
69599
+ compatible,
69600
+ nodeId: node.id,
69601
+ nodeWorkspace: node.workspace,
69602
+ exactWorkspace
69603
+ });
69604
+ emittedMesh = true;
69605
+ }
69606
+ }
69607
+ if (compatible && !emittedMesh) {
69608
+ matches.push({
69609
+ meshId: mesh.id,
69610
+ meshName: mesh.name,
69611
+ repoIdentity: normalizeRepoIdentity(mesh.repoIdentity),
69612
+ compatible: true
69613
+ });
69614
+ }
69615
+ }
69616
+ return matches;
69617
+ }
69618
+ function meshSummary(mesh) {
69619
+ return {
69620
+ id: mesh.id,
69621
+ name: mesh.name,
69622
+ repoIdentity: normalizeRepoIdentity(mesh.repoIdentity),
69623
+ defaultBranch: mesh.defaultBranch,
69624
+ nodeCount: mesh.nodes?.length || 0
69625
+ };
69626
+ }
69627
+ function addStep(mesh, discovery) {
69628
+ return {
69629
+ command: "add_mesh_node",
69630
+ description: "Explicitly register this existing checkout as a mesh node.",
69631
+ writes: true,
69632
+ approvalRequired: true,
69633
+ args: {
69634
+ meshId: mesh.id,
69635
+ workspace: discovery.repoRoot,
69636
+ repoRoot: discovery.repoRoot,
69637
+ isLocalWorktree: discovery.isLinkedWorktree
69638
+ }
69639
+ };
69640
+ }
69641
+ function initStep(discovery) {
69642
+ return {
69643
+ command: "mesh_init",
69644
+ description: "Preview suggested .adhdev configuration; persist only in a separate approved write call.",
69645
+ writes: false,
69646
+ approvalRequired: false,
69647
+ args: { workspace: discovery.repoRoot, write: false, overwrite: false }
69648
+ };
69649
+ }
69650
+ async function planMeshOnboarding(options) {
69651
+ const discoveryResult = await discoverGit(options.workspace);
69652
+ if (isPlanFailure(discoveryResult)) return discoveryResult;
69653
+ const discovery = discoveryResult;
69654
+ const operation = options.operation || "auto";
69655
+ const meshes = options.meshes || listMeshesReadOnly();
69656
+ const membership = await membershipsFor(discovery, meshes);
69657
+ const requestedMesh = options.meshId ? meshes.find((mesh) => mesh.id === options.meshId) : void 0;
69658
+ if (options.meshId && !requestedMesh) {
69659
+ return failure2("mesh_not_found", `Mesh not found: ${options.meshId}`, "List meshes and choose an existing mesh id.", { discovery, membership });
69660
+ }
69661
+ if (requestedMesh && normalizeRepoIdentity(requestedMesh.repoIdentity) !== discovery.repoIdentity) {
69662
+ return failure2(
69663
+ "unrelated_repo_identity",
69664
+ `Workspace identity '${discovery.repoIdentity}' does not match mesh '${requestedMesh.name}' identity '${normalizeRepoIdentity(requestedMesh.repoIdentity)}'.`,
69665
+ "Choose a mesh for the same repository, or create a separate mesh for this repository.",
69666
+ { discovery, membership }
69667
+ );
69668
+ }
69669
+ const compatibleMeshes = requestedMesh ? [requestedMesh] : meshes.filter((mesh) => normalizeRepoIdentity(mesh.repoIdentity) === discovery.repoIdentity);
69670
+ if (compatibleMeshes.length > 1) {
69671
+ return failure2(
69672
+ "ambiguous_mesh",
69673
+ `More than one compatible mesh exists for '${discovery.repoIdentity}': ${compatibleMeshes.map((mesh) => `${mesh.name} (${mesh.id})`).join(", ")}`,
69674
+ "Pass an explicit mesh id to select the intended mesh.",
69675
+ { discovery, membership }
69676
+ );
69677
+ }
69678
+ const compatibleMesh = compatibleMeshes[0];
69679
+ const duplicate = membership.find((match) => match.compatible && match.nodeId && (!compatibleMesh || match.meshId === compatibleMesh.id));
69680
+ if (operation !== "clone_worktree" && duplicate) {
69681
+ return failure2(
69682
+ duplicate.exactWorkspace ? "duplicate_workspace" : "duplicate_node",
69683
+ `Workspace is already registered as node '${duplicate.nodeId}' in mesh '${duplicate.meshName}'.`,
69684
+ "Use the existing node, or choose clone_worktree with a new branch for isolated work.",
69685
+ { discovery, membership }
69686
+ );
69687
+ }
69688
+ if (operation === "add_existing" && !compatibleMesh) {
69689
+ return failure2(
69690
+ "mesh_not_found",
69691
+ `No compatible mesh exists for '${discovery.repoIdentity}'.`,
69692
+ "Run the auto/create onboarding plan first, explicitly create the mesh, then add this workspace.",
69693
+ { discovery, membership }
69694
+ );
69695
+ }
69696
+ if (operation === "clone_worktree") {
69697
+ const planWarnings = [];
69698
+ if (!compatibleMesh) {
69699
+ return failure2(
69700
+ "mesh_not_found",
69701
+ `No compatible mesh exists for '${discovery.repoIdentity}'.`,
69702
+ "Create and add the base workspace first, then plan a cloned worktree node.",
69703
+ { discovery, membership }
69704
+ );
69705
+ }
69706
+ const dirtyBehavior = mergeAndNormalizePolicy(compatibleMesh.policy, void 0).dirtyWorkspaceBehavior;
69707
+ if (discovery.dirty && dirtyBehavior === "block") {
69708
+ return failure2(
69709
+ "dirty_workspace",
69710
+ `Workspace has ${discovery.changedFileCount} uncommitted change(s); a cloned worktree would not include them.`,
69711
+ "Commit or stash the changes, then re-run the clone plan. (This mesh's dirtyWorkspaceBehavior is 'block'; set it to 'warn' to allow cloning from a dirty workspace.)",
69712
+ { discovery, membership }
69713
+ );
69714
+ }
69715
+ if (discovery.dirty) {
69716
+ planWarnings.push(
69717
+ `Source workspace has ${discovery.changedFileCount} uncommitted change(s). The new worktree is created from HEAD, so uncommitted changes are NOT included in it \u2014 commit them first if the cloned branch needs them.`
69718
+ );
69719
+ }
69720
+ const branch = (options.branch || "").trim();
69721
+ if (!branch || !await git(discovery.repoRoot, ["check-ref-format", "--branch", branch], true)) {
69722
+ return failure2(
69723
+ "unsafe_branch",
69724
+ branch ? `Requested worktree branch is unsafe: ${branch}` : "A named branch is required to clone a new worktree node.",
69725
+ "Provide a new valid branch name such as feat/my-task.",
69726
+ { discovery, membership }
69727
+ );
69728
+ }
69729
+ const sourceNode = duplicate?.nodeId ? compatibleMesh.nodes.find((node) => node.id === duplicate.nodeId) : compatibleMesh.nodes.find((node) => !!node.workspace);
69730
+ if (!sourceNode) {
69731
+ return failure2(
69732
+ "mesh_not_found",
69733
+ `Mesh '${compatibleMesh.name}' has no source node to clone.`,
69734
+ "Explicitly add this existing workspace as the first node, then clone a worktree.",
69735
+ { discovery, membership }
69736
+ );
69737
+ }
69738
+ const suggestedConfig2 = runMeshInit(compatibleMesh, discovery.repoRoot, options.detectedProviders || []);
69739
+ return {
69740
+ success: true,
69741
+ dryRun: true,
69742
+ discovery,
69743
+ membership,
69744
+ compatibleMesh: meshSummary(compatibleMesh),
69745
+ plan: {
69746
+ kind: "clone_new_worktree",
69747
+ summary: `Create an isolated worktree node from '${sourceNode.id}' on branch '${branch}'.`,
69748
+ // Only a 'block' mesh actually requires a clean source; under the
69749
+ // default 'warn' a dirty workspace is advisory, so reporting true here
69750
+ // would misdescribe the plan the caller is approving.
69751
+ requiresClean: dirtyBehavior === "block",
69752
+ approvalRequired: true,
69753
+ steps: [{
69754
+ command: "clone_mesh_node",
69755
+ description: "Explicitly create the branch/worktree and register the resulting node.",
69756
+ writes: true,
69757
+ approvalRequired: true,
69758
+ args: {
69759
+ meshId: compatibleMesh.id,
69760
+ sourceNodeId: sourceNode.id,
69761
+ branch,
69762
+ baseBranch: compatibleMesh.defaultBranch || discovery.defaultBranch
69763
+ }
69764
+ }, initStep(discovery)],
69765
+ alternatives: [{ kind: "add_existing_workspace", summary: "Register the supplied checkout without creating a worktree.", requiresClean: false }]
69766
+ },
69767
+ suggestedConfig: suggestedConfig2,
69768
+ note: "Read-only plan only. No mesh, config, branch, remote or worktree state was changed.",
69769
+ ...planWarnings.length ? { warnings: planWarnings } : {}
69770
+ };
69771
+ }
69772
+ const suggestedConfig = runMeshInit(compatibleMesh || {}, discovery.repoRoot, options.detectedProviders || []);
69773
+ if (compatibleMesh && operation !== "create_mesh") {
69774
+ return {
69775
+ success: true,
69776
+ dryRun: true,
69777
+ discovery,
69778
+ membership,
69779
+ compatibleMesh: meshSummary(compatibleMesh),
69780
+ plan: {
69781
+ kind: "add_existing_workspace",
69782
+ summary: discovery.isLinkedWorktree ? `Register the existing linked worktree on '${discovery.currentBranch}' in '${compatibleMesh.name}'.` : `Register the main checkout as a base node in '${compatibleMesh.name}'.`,
69783
+ requiresClean: false,
69784
+ approvalRequired: true,
69785
+ steps: [addStep(compatibleMesh, discovery), initStep(discovery)],
69786
+ alternatives: [{
69787
+ kind: "clone_new_worktree",
69788
+ summary: `After a base node exists, create a clean isolated worktree from '${compatibleMesh.defaultBranch || discovery.defaultBranch}'.`,
69789
+ requiresClean: true
69790
+ }]
69791
+ },
69792
+ suggestedConfig,
69793
+ note: "Read-only plan only. Adding the node and writing suggested configs require separate explicit approved calls."
69794
+ };
69795
+ }
69796
+ const identityRepoName = discovery.repoIdentity.split("/").filter(Boolean).at(-1);
69797
+ const suggestedName = `${identityRepoName || (0, import_node_path2.basename)(discovery.mainWorktreeRoot || discovery.repoRoot)}-mesh`;
69798
+ const createArgs = {
69799
+ name: suggestedName,
69800
+ repoIdentity: discovery.repoIdentity,
69801
+ repoRemoteUrl: discovery.origin?.urls[0] || discovery.upstream?.urls[0] || discovery.remotes[0]?.urls[0],
69802
+ defaultBranch: discovery.defaultBranch
69803
+ };
69804
+ return {
69805
+ success: true,
69806
+ dryRun: true,
69807
+ discovery,
69808
+ membership,
69809
+ plan: {
69810
+ kind: "create_mesh_and_onboard",
69811
+ summary: `Create '${suggestedName}', add this ${discovery.isLinkedWorktree ? "linked worktree" : "checkout"} as its first node, then review suggested .adhdev configs.`,
69812
+ requiresClean: false,
69813
+ approvalRequired: true,
69814
+ steps: [
69815
+ {
69816
+ command: "create_mesh",
69817
+ description: "Explicitly create a machine-local mesh record.",
69818
+ writes: true,
69819
+ approvalRequired: true,
69820
+ args: createArgs
69821
+ },
69822
+ {
69823
+ command: "add_mesh_node",
69824
+ description: "After creation, explicitly register this existing workspace as the first node.",
69825
+ writes: true,
69826
+ approvalRequired: true,
69827
+ args: {
69828
+ meshId: "<created mesh id>",
69829
+ workspace: discovery.repoRoot,
69830
+ repoRoot: discovery.repoRoot,
69831
+ isLocalWorktree: discovery.isLinkedWorktree
69832
+ }
69833
+ },
69834
+ initStep(discovery)
69835
+ ],
69836
+ alternatives: []
69837
+ },
69838
+ suggestedConfig,
69839
+ note: "Read-only plan only. No mesh/config/worktree writes occurred; execute each write step separately after approval."
69840
+ };
69841
+ }
69842
+ var import_node_child_process5;
69843
+ var import_promises5;
69844
+ var import_node_path2;
69845
+ var import_node_util4;
69846
+ var execFileAsync3;
69847
+ var GIT_TIMEOUT_MS2;
69848
+ var init_mesh_onboarding_plan = __esm2({
69849
+ "src/mesh/mesh-onboarding-plan.ts"() {
69850
+ "use strict";
69851
+ import_node_child_process5 = require("child_process");
69852
+ import_promises5 = require("fs/promises");
69853
+ import_node_path2 = require("path");
69854
+ import_node_util4 = require("util");
69855
+ init_git_locale();
69856
+ init_repo_mesh_types();
69857
+ init_mesh_config();
69858
+ init_mesh_init();
69859
+ init_git_worktree();
69860
+ execFileAsync3 = (0, import_node_util4.promisify)(import_node_child_process5.execFile);
69861
+ GIT_TIMEOUT_MS2 = 15e3;
69862
+ }
69863
+ });
69864
+ var mesh_coordinator_exports = {};
69865
+ __export2(mesh_coordinator_exports, {
69866
+ applyMeshCoordinatorSystemPromptInjection: () => applyMeshCoordinatorSystemPromptInjection,
69867
+ buildMeshCoordinatorRegistrationPlan: () => buildMeshCoordinatorRegistrationPlan,
69868
+ cleanupCoordinatorAgentFile: () => cleanupCoordinatorAgentFile,
69869
+ createHermesManualMeshCoordinatorSetup: () => createHermesManualMeshCoordinatorSetup,
69870
+ execUnderPty: () => execUnderPty,
69871
+ inspectMeshCoordinatorMcpServerPaths: () => inspectMeshCoordinatorMcpServerPaths,
69872
+ resolveMeshCoordinatorSetup: () => resolveMeshCoordinatorSetup,
69873
+ stripCoordinatorWrapperFile: () => stripCoordinatorWrapperFile
69874
+ });
69875
+ function isHermesProvider(provider, cliType) {
69876
+ const type2 = cliType?.trim() || provider?.type?.trim() || "";
69877
+ return type2 === HERMES_CLI_TYPE;
69878
+ }
69879
+ function resolveHermesMeshCoordinatorSetup(options) {
69880
+ const mcpServer = resolveAdhdevMcpServerLaunch({
69881
+ meshId: options.meshId,
69882
+ adhdevMcpCommand: options.adhdevMcpCommand,
69883
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
69884
+ nodeExecutable: options.nodeExecutable,
69885
+ adhdevMcpTransport: options.adhdevMcpTransport,
69886
+ adhdevMcpPort: options.adhdevMcpPort
69887
+ });
69888
+ if (!mcpServer) {
69889
+ return {
69890
+ kind: "unsupported",
69891
+ reason: "Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode"
69892
+ };
69893
+ }
69894
+ const configPath = (0, import_node_path3.join)(resolveHermesCoordinatorHome(options.meshId, options.workspace), "config.yaml");
69895
+ if (!configPath.trim()) {
69896
+ return createHermesManualMeshCoordinatorSetup(options.meshId, options.workspace);
69897
+ }
69898
+ return {
69899
+ kind: "auto_import",
69900
+ serverName: DEFAULT_SERVER_NAME,
69901
+ configPath,
69902
+ configFormat: "hermes_config_yaml",
69903
+ mcpServer
69904
+ };
69905
+ }
69906
+ function createHermesManualMeshCoordinatorSetup(meshId, workspace) {
69907
+ return {
69908
+ kind: "manual",
69909
+ serverName: DEFAULT_SERVER_NAME,
69910
+ configFormat: "hermes_config_yaml",
69911
+ configPathCommand: HERMES_MCP_CONFIG_PATH,
69912
+ requiresRestart: true,
69913
+ instructions: "Hermes CLI does not auto-import repo-local .mcp.json. Add this MCP server to Hermes config under mcp_servers, then start a fresh Hermes session.",
69914
+ template: renderMeshCoordinatorTemplate(
69915
+ "mcp_servers:\n {{serverName}}:\n command: {{adhdevMcpCommand}}\n args:\n - mcp\n - --mode\n - ipc\n - --repo-mesh\n - {{meshId}}\n enabled: true\n",
69916
+ {
69917
+ meshId,
69918
+ workspace,
69919
+ serverName: DEFAULT_SERVER_NAME,
69920
+ adhdevMcpCommand: DEFAULT_ADHDEV_MCP_COMMAND
69921
+ }
69922
+ )
69923
+ };
69924
+ }
69925
+ function resolveMeshCoordinatorSetup(options) {
69926
+ const { provider, meshId, workspace } = options;
69927
+ const config2 = provider?.meshCoordinator;
69928
+ if (!config2?.supported) {
69929
+ return {
69930
+ kind: "unsupported",
69931
+ reason: config2?.reason || "Provider does not declare Repo Mesh coordinator support"
69932
+ };
69933
+ }
69934
+ if (isHermesProvider(provider, options.cliType)) {
69935
+ return resolveHermesMeshCoordinatorSetup(options);
69936
+ }
69937
+ const mcpConfig = config2.mcpConfig;
69938
+ if (!mcpConfig || mcpConfig.mode === "none") {
69939
+ return {
69940
+ kind: "unsupported",
69941
+ reason: config2.reason || "Provider does not declare a usable Repo Mesh MCP configuration mode"
69942
+ };
69943
+ }
69944
+ const serverName = mcpConfig.serverName?.trim() || DEFAULT_SERVER_NAME;
69945
+ if (mcpConfig.mode === "auto_import") {
69946
+ const path63 = mcpConfig.path?.trim();
69947
+ if (!path63) {
69948
+ return { kind: "unsupported", reason: "Provider auto-import MCP config is missing a config path" };
69949
+ }
69950
+ const mcpServer = resolveAdhdevMcpServerLaunch({
69951
+ meshId,
69952
+ adhdevMcpCommand: options.adhdevMcpCommand,
69953
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
69954
+ nodeExecutable: options.nodeExecutable,
69955
+ adhdevMcpTransport: options.adhdevMcpTransport,
69956
+ adhdevMcpPort: options.adhdevMcpPort
69957
+ });
69958
+ if (!mcpServer) {
69959
+ return {
69960
+ kind: "unsupported",
69961
+ reason: "Could not resolve the ADHDev MCP server entrypoint and a Node runtime with WebSocket support for daemon IPC mode"
69962
+ };
69963
+ }
69964
+ return {
69965
+ kind: "auto_import",
69966
+ serverName,
69967
+ configPath: resolveMcpConfigPath(path63, workspace),
69968
+ configFormat: mcpConfig.format,
69969
+ mcpServer
69970
+ };
69971
+ }
69972
+ if (mcpConfig.mode === "manual") {
69973
+ const instructions = mcpConfig.instructions?.trim();
69974
+ const template = mcpConfig.template;
69975
+ if (!instructions || !template?.trim()) {
69976
+ return { kind: "unsupported", reason: "Provider manual MCP setup is missing instructions or template" };
69977
+ }
69978
+ const mcpServer = resolveAdhdevMcpServerLaunch({
69979
+ meshId,
69980
+ adhdevMcpCommand: options.adhdevMcpCommand,
69981
+ adhdevMcpEntryPath: options.adhdevMcpEntryPath,
69982
+ nodeExecutable: options.nodeExecutable,
69983
+ adhdevMcpTransport: options.adhdevMcpTransport,
69984
+ adhdevMcpPort: options.adhdevMcpPort
69985
+ });
69986
+ if (!mcpServer) {
69987
+ return {
69988
+ kind: "unsupported",
69989
+ reason: "Could not resolve the ADHDev MCP server entrypoint and transport arguments"
69990
+ };
69991
+ }
69992
+ let renderedTemplate = renderMeshCoordinatorTemplate(template, {
69993
+ meshId,
69994
+ workspace,
69995
+ serverName,
69996
+ adhdevMcpCommand: mcpServer.command,
69997
+ adhdevMcpArgs: mcpServer.args.join(" ")
69998
+ });
69999
+ const isCliCommand = !renderedTemplate.trim().includes("\n") && !renderedTemplate.trim().startsWith("{");
70000
+ if (isCliCommand) {
70001
+ if (!/\{\{\s*adhdevMcpArgs\s*\}\}/.test(template)) {
70002
+ renderedTemplate = replaceLegacyCliCommandMcpArgs(renderedTemplate, mcpServer.args);
70003
+ }
70004
+ return {
70005
+ kind: "cli_command",
70006
+ serverName,
70007
+ command: renderedTemplate.trim(),
70008
+ requiresRestart: mcpConfig.requiresRestart === true,
70009
+ instructions,
70010
+ mcpServer
70011
+ };
70012
+ }
70013
+ return {
70014
+ kind: "manual",
70015
+ serverName,
70016
+ configFormat: mcpConfig.format,
70017
+ configPathCommand: mcpConfig.configPathCommand,
70018
+ requiresRestart: mcpConfig.requiresRestart === true,
70019
+ instructions,
70020
+ template: renderedTemplate
70021
+ };
70022
+ }
70023
+ return {
70024
+ kind: "unsupported",
70025
+ reason: `Unsupported Repo Mesh MCP configuration mode: ${String(mcpConfig.mode)}`
70026
+ };
70027
+ }
70028
+ function renderMeshCoordinatorTemplate(template, values) {
70029
+ return template.replace(/\{\{\s*(meshId|workspace|serverName|adhdevMcpCommand|adhdevMcpArgs)\s*\}\}/g, (_, key2) => values[key2] || "");
70030
+ }
70031
+ function replaceLegacyCliCommandMcpArgs(command, args) {
70032
+ return command.replace(
70033
+ /\bmcp\s+--mode\s+(?:ipc|local)\s+--repo-mesh\s+\S+(?:\s+--port\s+\d+)?\s*$/,
70034
+ args.join(" ")
70035
+ );
70036
+ }
70037
+ function resolveHermesCoordinatorHome(meshId, workspace) {
70038
+ const key2 = `${meshId || "mesh"}
70039
+ ${(0, import_node_path3.resolve)(workspace || os13.tmpdir())}`;
70040
+ const hash2 = shortHash(key2);
70041
+ return (0, import_node_path3.join)(os13.tmpdir(), `adhdev-hermes-mesh-coordinator-${hash2}`);
70042
+ }
70043
+ function resolveMcpConfigPath(configPath, workspace) {
70044
+ const trimmed = configPath.trim();
70045
+ if (trimmed === "~") return os13.homedir();
70046
+ if (trimmed.startsWith("~/")) return (0, import_node_path3.join)(os13.homedir(), trimmed.slice(2));
70047
+ if ((0, import_node_path3.isAbsolute)(trimmed)) return trimmed;
70048
+ return (0, import_node_path3.join)(workspace, trimmed);
70049
+ }
70050
+ function resolveAdhdevMcpServerLaunch(options) {
70051
+ const directEntryPath = resolveAdhdevMcpEntryPath(options.adhdevMcpEntryPath);
70052
+ if (directEntryPath) {
70053
+ const transport2 = resolveMcpTransport(options.adhdevMcpTransport);
70054
+ const args2 = [directEntryPath, "--mode", transport2, "--repo-mesh", options.meshId];
70055
+ const port2 = resolveMcpPort(options.adhdevMcpPort);
70056
+ if (port2 !== void 0) args2.push("--port", String(port2));
70057
+ return {
70058
+ command: resolveNodeExecutable(options.nodeExecutable),
70059
+ args: args2
70060
+ };
70061
+ }
70062
+ const command = resolveAdhdevCommand(options.adhdevMcpCommand);
70063
+ const transport = resolveMcpTransport(options.adhdevMcpTransport);
70064
+ const directMcpEntrypoint = (0, import_node_path3.basename)(command).startsWith("adhdev-mcp") || command.includes("/vendor/mcp-server/") || command.includes("\\vendor\\mcp-server\\");
70065
+ const args = [...directMcpEntrypoint ? [] : ["mcp"], "--mode", transport, "--repo-mesh", options.meshId];
70066
+ const port = resolveMcpPort(options.adhdevMcpPort);
70067
+ if (port !== void 0) args.push("--port", String(port));
70068
+ return {
70069
+ command,
70070
+ args
70071
+ };
70072
+ }
70073
+ function resolveAdhdevCommand(explicitCommand) {
70074
+ return explicitCommand?.trim() || process.env.ADHDEV_COORDINATOR_MCP_COMMAND?.trim() || DEFAULT_ADHDEV_MCP_COMMAND;
70075
+ }
70076
+ function inspectMeshCoordinatorMcpServerPaths(mcpServer, options = {}) {
70077
+ const embeddedPaths = [mcpServer.command, ...mcpServer.args].filter((value) => (0, import_node_path3.isAbsolute)(value));
70078
+ if (embeddedPaths.length === 0) {
70079
+ return { state: "absent", referencedPath: null, volatile: false, volatileReason: null, warning: null };
70080
+ }
70081
+ let firstMissing = null;
70082
+ let firstVolatile = null;
70083
+ for (const embeddedPath of embeddedPaths) {
70084
+ const health = inspectEmbeddedPath(embeddedPath, options.env ?? process.env);
70085
+ if (health.state === "missing" && !firstMissing) firstMissing = health;
70086
+ if (health.volatile && !firstVolatile) firstVolatile = health;
70087
+ }
70088
+ const serverName = options.serverName?.trim() || "adhdev-mesh";
70089
+ const target = options.target?.trim() || `MCP setup for ${serverName}`;
70090
+ const propagationNote = options.repoLocal ? " The config file lives inside the workspace and may be committed \u2014 this machine-specific absolute path would propagate to teammates' machines, where it matches nothing." : "";
70091
+ if (firstMissing) {
70092
+ return {
70093
+ state: "missing",
70094
+ referencedPath: firstMissing.referencedPath,
70095
+ volatile: firstMissing.volatile || Boolean(firstVolatile),
70096
+ volatileReason: firstMissing.volatileReason || firstVolatile?.volatileReason || null,
70097
+ warning: `${target} embeds ${firstMissing.referencedPath}, which does not exist on this machine \u2014 the ${serverName} MCP server will fail to start (dangling reference, the statusline failure class).${propagationNote}`
70098
+ };
70099
+ }
70100
+ if (firstVolatile) {
70101
+ return {
70102
+ state: "ok",
70103
+ referencedPath: firstVolatile.referencedPath,
70104
+ volatile: true,
70105
+ volatileReason: firstVolatile.volatileReason,
70106
+ warning: `${target} embeds ${firstVolatile.referencedPath}, which is ${firstVolatile.volatileReason}. It works today, but the ${serverName} entry will dangle once that location is cleaned up \u2014 the same failure class as the 2026-08-20 statusline incident.${propagationNote}`
70107
+ };
70108
+ }
70109
+ return { state: "ok", referencedPath: embeddedPaths[0], volatile: false, volatileReason: null, warning: null };
70110
+ }
70111
+ function resolveAdhdevMcpEntryPath(explicitEntryPath) {
70112
+ const entryPath = explicitEntryPath?.trim() || process.env.ADHDEV_COORDINATOR_MCP_ENTRY_PATH?.trim();
70113
+ return entryPath || null;
70114
+ }
70115
+ function resolveNodeExecutable(explicitNodeExecutable) {
70116
+ return explicitNodeExecutable?.trim() || process.env.ADHDEV_COORDINATOR_NODE_EXECUTABLE?.trim() || process.execPath;
70117
+ }
70118
+ function resolveMcpTransport(explicitTransport) {
69407
70119
  if (explicitTransport === "local" || explicitTransport === "ipc") return explicitTransport;
69408
70120
  const envTransport = process.env.ADHDEV_COORDINATOR_MCP_TRANSPORT?.trim();
69409
70121
  return envTransport === "local" ? "local" : "ipc";
@@ -73288,33 +74000,24 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
73288
74000
  }
73289
74001
  return null;
73290
74002
  }
73291
- function isStructurallyUnmeasurable(quota) {
73292
- const kind = quota.metadata?.failureKind;
73293
- return typeof kind === "string" && STRUCTURALLY_UNMEASURABLE_FAILURE_KINDS.has(kind);
73294
- }
73295
74003
  function expiryRiskForRanking(node, providerType, axis, policy, now = Date.now(), context) {
73296
74004
  const entry = quotaEntryFor(node, providerType, context, now);
73297
74005
  if (!entry) return void 0;
73298
74006
  const { facts, quota } = entry;
73299
- const fresh = isQuotaSnapshotFresh(facts, quota, policy, now);
73300
- const confidence = quota.status === "ok" && fresh ? CONFIDENCE_CURRENT : isStructurallyUnmeasurable(quota) ? CONFIDENCE_RETAINED_STRUCTURAL : CONFIDENCE_RETAINED_AGED;
73301
74007
  const window = axis === "session" ? quota.session : quota.weekly;
73302
74008
  if (!isWindowTrustworthy(window, facts, quota, policy, now)) return void 0;
73303
74009
  const remaining = remainingPercent(window);
73304
74010
  if (remaining === void 0) return void 0;
73305
74011
  const resetsAt = Number(window?.resetsAt);
73306
- if (!Number.isFinite(resetsAt) || resetsAt <= 0) return { remainingPercent: remaining, risk: 0, confidence };
74012
+ if (!Number.isFinite(resetsAt) || resetsAt <= 0) return { remainingPercent: remaining, risk: 0 };
73307
74013
  const ageMs2 = quotaSnapshotAgeMs(facts, quota, now);
73308
- if (!Number.isFinite(ageMs2)) return { remainingPercent: remaining, risk: 0, confidence };
74014
+ if (!Number.isFinite(ageMs2)) return { remainingPercent: remaining, risk: 0 };
73309
74015
  const reporterNowMs = Number(quota.updatedAt) + ageMs2;
73310
74016
  const windowMinutes3 = Number(window?.windowMinutes);
73311
74017
  const fallbackMinutes = axis === "session" ? DEFAULT_SESSION_WINDOW_MINUTES : DEFAULT_WEEKLY_WINDOW_MINUTES;
73312
74018
  const windowMs = (Number.isFinite(windowMinutes3) && windowMinutes3 > 0 ? windowMinutes3 : fallbackMinutes) * 60 * 1e3;
73313
74019
  const elapsedFraction = Math.min(1, Math.max(0, 1 - (resetsAt - reporterNowMs) / windowMs));
73314
- return { remainingPercent: remaining, risk: remaining * elapsedFraction, confidence };
73315
- }
73316
- function rankedRisk(reading) {
73317
- return reading.risk * reading.confidence;
74020
+ return { remainingPercent: remaining, risk: remaining * elapsedFraction };
73318
74021
  }
73319
74022
  function rankProvidersByQuotaGate(node, orderedProviderTypes, policy, now = Date.now(), context) {
73320
74023
  const clear = [];
@@ -73339,19 +74042,14 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
73339
74042
  const sa = sessionByProvider.get(a);
73340
74043
  const sb = sessionByProvider.get(b);
73341
74044
  if (sa !== void 0 && sb !== void 0) {
73342
- const ra2 = rankedRisk(sa);
73343
- const rb2 = rankedRisk(sb);
73344
- if (rb2 !== ra2) return rb2 - ra2;
74045
+ if (sb.risk !== sa.risk) return sb.risk - sa.risk;
73345
74046
  if (sb.remainingPercent !== sa.remainingPercent) {
73346
74047
  return sb.remainingPercent - sa.remainingPercent;
73347
74048
  }
73348
74049
  } else if (sa !== void 0) return -1;
73349
74050
  else if (sb !== void 0) return 1;
73350
74051
  }
73351
- const ra = rankedRisk(wa);
73352
- const rb = rankedRisk(wb);
73353
- if (rb !== ra) return rb - ra;
73354
- if (wb.confidence !== wa.confidence) return wb.confidence - wa.confidence;
74052
+ if (wb.risk !== wa.risk) return wb.risk - wa.risk;
73355
74053
  return wb.remainingPercent - wa.remainingPercent;
73356
74054
  });
73357
74055
  const rankingEvidence = clear.map((providerType) => {
@@ -73363,8 +74061,6 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
73363
74061
  axis,
73364
74062
  ...reading ? {
73365
74063
  risk: reading.risk,
73366
- confidence: reading.confidence,
73367
- rankedRisk: rankedRisk(reading),
73368
74064
  remainingPercent: reading.remainingPercent
73369
74065
  } : {}
73370
74066
  };
@@ -73406,8 +74102,7 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
73406
74102
  axis,
73407
74103
  ...w ? {
73408
74104
  remainingPercent: w.remainingPercent,
73409
- risk: w.risk,
73410
- ...w.confidence !== CONFIDENCE_CURRENT ? { confidence: w.confidence, rankedRisk: rankedRisk(w) } : {}
74105
+ risk: w.risk
73411
74106
  } : {}
73412
74107
  };
73413
74108
  });
@@ -73507,10 +74202,6 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
73507
74202
  var absentFailOpenLoggedAt;
73508
74203
  var DEFAULT_WEEKLY_WINDOW_MINUTES;
73509
74204
  var DEFAULT_SESSION_WINDOW_MINUTES;
73510
- var CONFIDENCE_CURRENT;
73511
- var CONFIDENCE_RETAINED_STRUCTURAL;
73512
- var CONFIDENCE_RETAINED_AGED;
73513
- var STRUCTURALLY_UNMEASURABLE_FAILURE_KINDS;
73514
74205
  var RATIONALE_LOSERS_MAX;
73515
74206
  var lastQuotaRankingByNode;
73516
74207
  var init_mesh_quota_routing = __esm2({
@@ -73528,10 +74219,6 @@ The mesh has no work in flight. For each mission, decide its outcome: continue i
73528
74219
  absentFailOpenLoggedAt = /* @__PURE__ */ new Map();
73529
74220
  DEFAULT_WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
73530
74221
  DEFAULT_SESSION_WINDOW_MINUTES = 5 * 60;
73531
- CONFIDENCE_CURRENT = 1;
73532
- CONFIDENCE_RETAINED_STRUCTURAL = 0.7;
73533
- CONFIDENCE_RETAINED_AGED = 0.4;
73534
- STRUCTURALLY_UNMEASURABLE_FAILURE_KINDS = /* @__PURE__ */ new Set(["no-data"]);
73535
74222
  RATIONALE_LOSERS_MAX = 4;
73536
74223
  lastQuotaRankingByNode = /* @__PURE__ */ new Map();
73537
74224
  }
@@ -91836,6 +92523,16 @@ ${cleanBody}`;
91836
92523
  _acpValidator = null;
91837
92524
  }
91838
92525
  });
92526
+ function truncateValidationOutput(value) {
92527
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
92528
+ if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
92529
+ const head = text.slice(0, REFINE_VALIDATION_SUMMARY_HEAD_CHARS);
92530
+ const tail = text.slice(text.length - REFINE_VALIDATION_SUMMARY_TAIL_CHARS);
92531
+ const omitted = text.length - REFINE_VALIDATION_SUMMARY_HEAD_CHARS - REFINE_VALIDATION_SUMMARY_TAIL_CHARS;
92532
+ return `${head}
92533
+ [... ${omitted} chars omitted ...]
92534
+ ${tail}`;
92535
+ }
91839
92536
  function readChangedGitlinkPaths(repoRoot, fromRef, toRef) {
91840
92537
  try {
91841
92538
  const output = (0, import_node_child_process9.execFileSync)(GIT2, ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
@@ -91899,7 +92596,7 @@ ${cleanBody}`;
91899
92596
  }
91900
92597
  }
91901
92598
  function probeSubmoduleGitlinkReachability(input) {
91902
- const { path: path63, baseCommit, branchCommit, probeRoot, baseRepoRoot } = input;
92599
+ const { path: path63, baseCommit, branchCommit, probeRoot, baseRepoRoot, defaultBranch } = input;
91903
92600
  const submoduleRepo = (0, import_path17.resolve)(probeRoot, path63);
91904
92601
  const baseSubmoduleRepo = (0, import_path17.resolve)(baseRepoRoot, path63);
91905
92602
  if (baseCommit) ensureSubmoduleCommitLocal(submoduleRepo, baseSubmoduleRepo, baseCommit);
@@ -91913,7 +92610,7 @@ ${cleanBody}`;
91913
92610
  if (ff === "undeterminable") undeterminable.push("fastForward");
91914
92611
  else fastForward = ff;
91915
92612
  }
91916
- const reach = probeGitAncestry(submoduleRepo, branchCommit, "refs/remotes/origin/main");
92613
+ const reach = probeGitAncestry(submoduleRepo, branchCommit, `refs/remotes/origin/${defaultBranch || "main"}`);
91917
92614
  if (reach === "undeterminable") undeterminable.push("reachableFromOriginMain");
91918
92615
  else reachableFromOriginMain = reach;
91919
92616
  }
@@ -92069,11 +92766,251 @@ ${cleanBody}`;
92069
92766
  parts.push("Do not merge the root branch until every submodule gitlink commit is reachable from submodule origin/main.");
92070
92767
  return parts.join(" ");
92071
92768
  }
92769
+ async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree, options = {}) {
92770
+ const startedAt = Date.now();
92771
+ const entries = [];
92772
+ try {
92773
+ const { execFile: execFile9 } = await import("child_process");
92774
+ const { promisify: promisify12 } = await import("util");
92775
+ const execFileAsync8 = promisify12(execFile9);
92776
+ const runGit4 = async (cwd, args) => {
92777
+ const { stdout } = await execFileAsync8(GIT2, args, {
92778
+ cwd,
92779
+ encoding: "utf8",
92780
+ timeout: 3e4,
92781
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
92782
+ windowsHide: true
92783
+ });
92784
+ return String(stdout || "");
92785
+ };
92786
+ const verifyRemoteMainContainsCommit = (submodulePath, commit, branch = "main") => verifyRemoteBranchContainsCommit(runGit4, submodulePath, commit, branch);
92787
+ const publishCommitToRemoteMain = async (submodulePath, commit, branch = "main") => {
92788
+ const refspec = `${commit}:refs/heads/${branch}`;
92789
+ const { stdout, stderr } = await execFileAsync8(GIT2, ["push", "origin", refspec], {
92790
+ cwd: submodulePath,
92791
+ encoding: "utf8",
92792
+ timeout: 3e4,
92793
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
92794
+ windowsHide: true
92795
+ });
92796
+ return { stdout: String(stdout || ""), stderr: String(stderr || ""), refspec };
92797
+ };
92798
+ const importCommitFromWorktreeSubmodule = async (submodulePath, worktreeSubmodulePath, commit) => {
92799
+ if (!fs50.existsSync(worktreeSubmodulePath)) return false;
92800
+ try {
92801
+ await runGit4(worktreeSubmodulePath, ["cat-file", "-e", `${commit}^{commit}`]);
92802
+ } catch {
92803
+ return false;
92804
+ }
92805
+ await runGit4(submodulePath, ["-c", "protocol.file.allow=always", "fetch", worktreeSubmodulePath, commit]);
92806
+ await runGit4(submodulePath, ["cat-file", "-e", `${commit}^{commit}`]);
92807
+ return true;
92808
+ };
92809
+ const findEquivalentPublishedCommit = async (submodulePath, commit, remoteRef) => {
92810
+ try {
92811
+ const targetTree = (await runGit4(submodulePath, ["rev-parse", `${commit}^{tree}`])).trim();
92812
+ if (!targetTree) return void 0;
92813
+ const candidates = (await runGit4(submodulePath, ["rev-list", "--max-count=100", remoteRef])).split("\n").map((s2) => s2.trim()).filter(Boolean);
92814
+ for (const candidate of candidates) {
92815
+ let tree = "";
92816
+ try {
92817
+ tree = (await runGit4(submodulePath, ["rev-parse", `${candidate}^{tree}`])).trim();
92818
+ } catch {
92819
+ continue;
92820
+ }
92821
+ if (tree && tree === targetTree) return candidate;
92822
+ }
92823
+ } catch {
92824
+ }
92825
+ return void 0;
92826
+ };
92827
+ const treeOutput = await runGit4(repoRoot, ["ls-tree", "-r", "-z", mergedTree]);
92828
+ const gitlinks = treeOutput.split("\0").filter(Boolean).map((record2) => {
92829
+ const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record2);
92830
+ return match ? { commit: match[1], path: match[2] } : null;
92831
+ }).filter((entry) => !!entry);
92832
+ for (const gitlink of gitlinks) {
92833
+ const submodulePath = (0, import_path17.resolve)(repoRoot, gitlink.path);
92834
+ const entry = {
92835
+ path: gitlink.path,
92836
+ commit: gitlink.commit,
92837
+ reachable: false
92838
+ };
92839
+ let submoduleDefaultBranch = "main";
92840
+ try {
92841
+ if (!fs50.existsSync(submodulePath)) {
92842
+ entry.error = `Submodule checkout missing at ${gitlink.path}`;
92843
+ entry.publishRequired = true;
92844
+ if (options.allowAutoPublishSubmoduleMainCommits === true) {
92845
+ entry.autoPublishAllowed = true;
92846
+ entry.autoPublishAttempted = false;
92847
+ entry.autoPublishSkippedReason = `submodule checkout missing at ${gitlink.path}; cannot perform non-force push to origin/main`;
92848
+ }
92849
+ entries.push(entry);
92850
+ continue;
92851
+ }
92852
+ entry.checkedLocal = true;
92853
+ try {
92854
+ await runGit4(submodulePath, ["cat-file", "-e", `${gitlink.commit}^{commit}`]);
92855
+ entry.localReachable = true;
92856
+ } catch {
92857
+ entry.localReachable = false;
92858
+ if (options.allowAutoPublishSubmoduleMainCommits === true && options.worktreeRoot) {
92859
+ try {
92860
+ const imported = await importCommitFromWorktreeSubmodule(submodulePath, (0, import_path17.resolve)(options.worktreeRoot, gitlink.path), gitlink.commit);
92861
+ if (imported) {
92862
+ entry.localReachable = true;
92863
+ entry.importedFromWorktree = true;
92864
+ }
92865
+ } catch (importError) {
92866
+ entry.autoPublishSkippedReason = `candidate commit was not present in the source checkout and could not be imported from worktree submodule: ${truncateValidationOutput(importError?.stderr || importError?.message || String(importError))}`;
92867
+ }
92868
+ }
92869
+ }
92870
+ try {
92871
+ entry.remote = "origin";
92872
+ let remoteUrl = "";
92873
+ try {
92874
+ remoteUrl = (await runGit4(submodulePath, ["remote", "get-url", "origin"])).trim();
92875
+ if (!remoteUrl) throw new Error("origin remote has no URL");
92876
+ entry.remoteUrl = remoteUrl;
92877
+ } catch {
92878
+ entry.error = "Submodule remote reachability check failed: no configured origin remote";
92879
+ entry.publishRequired = true;
92880
+ if (options.allowAutoPublishSubmoduleMainCommits === true) {
92881
+ entry.autoPublishAllowed = true;
92882
+ entry.autoPublishAttempted = false;
92883
+ entry.autoPublishSkippedReason = "submodule origin remote is not configured; cannot perform non-force push to origin/main";
92884
+ }
92885
+ entries.push(entry);
92886
+ continue;
92887
+ }
92888
+ submoduleDefaultBranch = await resolveSubmoduleDefaultBranch({
92889
+ submoduleRepoPath: submodulePath,
92890
+ superprojectWorkspace: repoRoot,
92891
+ submodulePath: gitlink.path
92892
+ });
92893
+ entry.remoteMainBranch = submoduleDefaultBranch;
92894
+ const verdict = await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, submoduleDefaultBranch);
92895
+ if (verdict.state === "contained") {
92896
+ entry.fetchedFromOrigin = true;
92897
+ entry.remoteReachable = true;
92898
+ entry.remoteMainReachable = true;
92899
+ entry.reachable = true;
92900
+ } else if (verdict.state === "undeterminable") {
92901
+ const e = verdict.error;
92902
+ entry.remoteMainUndeterminable = true;
92903
+ entry.publishRequired = false;
92904
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
92905
+ entry.error = `Submodule remote main reachability for origin/${submoduleDefaultBranch} could not be determined (the remote was not successfully consulted): ${details}. This is not evidence that ${gitlink.commit} is unpublished \u2014 resolve the remote access problem and re-run; Refinery will not publish on an undetermined verdict.`;
92906
+ if (options.allowAutoPublishSubmoduleMainCommits === true) {
92907
+ entry.autoPublishAllowed = true;
92908
+ entry.autoPublishAttempted = false;
92909
+ entry.autoPublishSkippedReason = `remote main containment for origin/${submoduleDefaultBranch} could not be determined, so Refinery will not push \u2014 a failed fetch is not proof the commit is unpublished`;
92910
+ }
92911
+ } else {
92912
+ const e = verdict.error;
92913
+ entry.remoteReachable = false;
92914
+ entry.remoteMainReachable = false;
92915
+ const equivalentPublished = await findEquivalentPublishedCommit(submodulePath, gitlink.commit, `refs/remotes/origin/${submoduleDefaultBranch}`);
92916
+ if (equivalentPublished) {
92917
+ entry.equivalentPublishedCommit = equivalentPublished;
92918
+ entry.publishRequired = false;
92919
+ entry.error = `Submodule commit ${gitlink.commit} is not reachable from origin/${submoduleDefaultBranch}, but an equivalent commit ${equivalentPublished} (identical tree) is already published there; converge the gitlink to the published commit instead of publishing a same-content twin.`;
92920
+ if (options.allowAutoPublishSubmoduleMainCommits === true) {
92921
+ entry.autoPublishAllowed = true;
92922
+ entry.autoPublishAttempted = false;
92923
+ entry.autoPublishSkippedReason = `an equivalent commit (${equivalentPublished}) is already published on origin/${submoduleDefaultBranch}; publishing a same-content twin is wrong \u2014 converge the gitlink to the published commit instead`;
92924
+ }
92925
+ } else {
92926
+ entry.publishRequired = true;
92927
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
92928
+ entry.error = `Submodule remote main reachability check failed for origin/${submoduleDefaultBranch}: ${details}`;
92929
+ if (options.allowAutoPublishSubmoduleMainCommits === true && entry.localReachable === true) {
92930
+ entry.autoPublishAllowed = true;
92931
+ entry.autoPublishAttempted = true;
92932
+ try {
92933
+ const publish = await publishCommitToRemoteMain(submodulePath, gitlink.commit, submoduleDefaultBranch);
92934
+ entry.autoPublishRefspec = publish.refspec;
92935
+ entry.publishStdout = truncateValidationOutput(publish.stdout);
92936
+ entry.publishStderr = truncateValidationOutput(publish.stderr);
92937
+ entry.autoPublishSucceeded = true;
92938
+ const postPublish = await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, submoduleDefaultBranch);
92939
+ if (postPublish.state !== "contained") {
92940
+ entry.autoPublishVerified = false;
92941
+ const postDetails = truncateValidationOutput(
92942
+ postPublish.error?.stderr || postPublish.error?.message || String(postPublish.error)
92943
+ );
92944
+ if (postPublish.state === "undeterminable") entry.remoteMainUndeterminable = true;
92945
+ else entry.remoteMainReachable = false;
92946
+ entry.error = `Submodule auto-publish to origin/${submoduleDefaultBranch} reported success but post-publish verification did not confirm containment (${postPublish.state}): ${postDetails}`;
92947
+ } else {
92948
+ entry.fetchedFromOrigin = true;
92949
+ entry.remoteReachable = true;
92950
+ entry.remoteMainReachable = true;
92951
+ entry.autoPublishVerified = true;
92952
+ entry.publishRequired = false;
92953
+ entry.reachable = true;
92954
+ entry.error = void 0;
92955
+ }
92956
+ } catch (publishError) {
92957
+ entry.autoPublishSucceeded = false;
92958
+ entry.autoPublishVerified = false;
92959
+ const publishDetails = truncateValidationOutput(publishError?.stderr || publishError?.message || String(publishError));
92960
+ entry.error = `Submodule auto-publish to origin/${submoduleDefaultBranch} failed or could not be verified: ${publishDetails}`;
92961
+ }
92962
+ } else if (options.allowAutoPublishSubmoduleMainCommits === true) {
92963
+ entry.autoPublishAllowed = true;
92964
+ entry.autoPublishAttempted = false;
92965
+ entry.autoPublishSkippedReason = entry.autoPublishSkippedReason || `candidate commit is not reachable in the source checkout or worktree submodule, so Refinery cannot push it to origin/${submoduleDefaultBranch}`;
92966
+ }
92967
+ }
92968
+ }
92969
+ } catch (e) {
92970
+ entry.remoteReachable = false;
92971
+ entry.remoteMainReachable = false;
92972
+ entry.publishRequired = true;
92973
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
92974
+ entry.error = `Submodule remote main reachability check failed for origin/${submoduleDefaultBranch}: ${details}`;
92975
+ }
92976
+ } catch (e) {
92977
+ entry.error = truncateValidationOutput(e?.message || String(e));
92978
+ entry.publishRequired = true;
92979
+ }
92980
+ entries.push(entry);
92981
+ }
92982
+ const unreachable = entries.filter((entry) => !entry.reachable);
92983
+ return {
92984
+ status: unreachable.length ? "failed" : "passed",
92985
+ checked: entries.length,
92986
+ unreachable: unreachable.map((entry) => ({ ...entry, publishRequired: entry.publishRequired !== false })),
92987
+ entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
92988
+ durationMs: Date.now() - startedAt,
92989
+ autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
92990
+ autoPublishPolicySource: options.autoPublishPolicySource
92991
+ };
92992
+ } catch (e) {
92993
+ const unreachable = entries.filter((entry) => !entry.reachable).map((entry) => ({ ...entry, publishRequired: entry.publishRequired !== false }));
92994
+ return {
92995
+ status: "failed",
92996
+ checked: entries.length,
92997
+ unreachable,
92998
+ entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
92999
+ durationMs: Date.now() - startedAt,
93000
+ autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
93001
+ autoPublishPolicySource: options.autoPublishPolicySource,
93002
+ error: truncateValidationOutput(e?.message || String(e))
93003
+ };
93004
+ }
93005
+ }
92072
93006
  var fs50;
92073
93007
  var import_node_child_process9;
92074
93008
  var import_path17;
92075
93009
  var GIT2;
92076
93010
  var REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES;
93011
+ var REFINE_VALIDATION_SUMMARY_HEAD_CHARS;
93012
+ var REFINE_VALIDATION_SUMMARY_TAIL_CHARS;
93013
+ var REFINE_VALIDATION_SUMMARY_CHARS;
92077
93014
  var init_mesh_refine_gitlink_utils = __esm2({
92078
93015
  "src/mesh/mesh-refine-gitlink-utils.ts"() {
92079
93016
  "use strict";
@@ -92082,8 +93019,12 @@ ${cleanBody}`;
92082
93019
  import_path17 = require("path");
92083
93020
  init_logger();
92084
93021
  init_resolve_executable();
93022
+ init_worktree_bootstrap_config();
92085
93023
  GIT2 = process.platform === "win32" ? resolveWin32Executable("git") : "git";
92086
93024
  REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
93025
+ REFINE_VALIDATION_SUMMARY_HEAD_CHARS = 2e3;
93026
+ REFINE_VALIDATION_SUMMARY_TAIL_CHARS = 6e3;
93027
+ REFINE_VALIDATION_SUMMARY_CHARS = REFINE_VALIDATION_SUMMARY_HEAD_CHARS + REFINE_VALIDATION_SUMMARY_TAIL_CHARS;
92087
93028
  }
92088
93029
  });
92089
93030
  function resolveRefineVitestMaxWorkers(env2 = process.env) {
@@ -92464,16 +93405,6 @@ ${cleanBody}`;
92464
93405
  SUBMODULE_PUBLICATION_UNDETERMINABLE_RECOMMENDED_ACTION = "Restore access to the submodule remote (the publication check could not be run), then rerun mesh_refine_node. Do NOT publish and do NOT retry the rebase on this evidence: it is unknown whether the branch-side commit is already on origin, so minting or pushing here can synthesize a redundant twin of a published commit.";
92465
93406
  }
92466
93407
  });
92467
- function truncateValidationOutput(value) {
92468
- const text = typeof value === "string" ? value : value == null ? "" : String(value);
92469
- if (text.length <= REFINE_VALIDATION_SUMMARY_CHARS) return text;
92470
- const head = text.slice(0, REFINE_VALIDATION_SUMMARY_HEAD_CHARS);
92471
- const tail = text.slice(text.length - REFINE_VALIDATION_SUMMARY_TAIL_CHARS);
92472
- const omitted = text.length - REFINE_VALIDATION_SUMMARY_HEAD_CHARS - REFINE_VALIDATION_SUMMARY_TAIL_CHARS;
92473
- return `${head}
92474
- [... ${omitted} chars omitted ...]
92475
- ${tail}`;
92476
- }
92477
93408
  function classifyValidationFailure(error48, stderr, spawnResolutionFailed) {
92478
93409
  const outputLimitExceeded = error48?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || /maxBuffer length exceeded/i.test(String(error48?.message || ""));
92479
93410
  const missingDependencyFailure = !spawnResolutionFailed && !outputLimitExceeded && /Cannot find module|MODULE_NOT_FOUND|node_modules|command not found|not found/i.test(stderr);
@@ -92728,12 +93659,22 @@ ${e?.stderr || ""}`
92728
93659
  } catch {
92729
93660
  continue;
92730
93661
  }
93662
+ let gitlinkDefaultBranch;
93663
+ try {
93664
+ gitlinkDefaultBranch = await resolveSubmoduleDefaultBranch({
93665
+ submoduleRepoPath: (0, import_path19.resolve)(submoduleProbeRoot, p),
93666
+ superprojectWorkspace: repoRoot,
93667
+ submodulePath: p
93668
+ });
93669
+ } catch {
93670
+ }
92731
93671
  submoduleGitlinks.push(probeSubmoduleGitlinkReachability({
92732
93672
  path: p,
92733
93673
  baseCommit,
92734
93674
  branchCommit,
92735
93675
  probeRoot: submoduleProbeRoot,
92736
- baseRepoRoot: repoRoot
93676
+ baseRepoRoot: repoRoot,
93677
+ defaultBranch: gitlinkDefaultBranch
92737
93678
  }));
92738
93679
  }
92739
93680
  } catch {
@@ -93228,243 +94169,6 @@ ${mergeTreeErr?.stderr || ""}`;
93228
94169
  };
93229
94170
  }
93230
94171
  }
93231
- async function runMeshRefineSubmoduleReachabilityGate(repoRoot, mergedTree, options = {}) {
93232
- const startedAt = Date.now();
93233
- const entries = [];
93234
- try {
93235
- const { execFile: execFile9 } = await import("child_process");
93236
- const { promisify: promisify12 } = await import("util");
93237
- const execFileAsync8 = promisify12(execFile9);
93238
- const runGit4 = async (cwd, args) => {
93239
- const { stdout } = await execFileAsync8(GIT2, args, {
93240
- cwd,
93241
- encoding: "utf8",
93242
- timeout: 3e4,
93243
- maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
93244
- windowsHide: true
93245
- });
93246
- return String(stdout || "");
93247
- };
93248
- const verifyRemoteMainContainsCommit = (submodulePath, commit, branch = "main") => verifyRemoteBranchContainsCommit(runGit4, submodulePath, commit, branch);
93249
- const publishCommitToRemoteMain = async (submodulePath, commit, branch = "main") => {
93250
- const refspec = `${commit}:refs/heads/${branch}`;
93251
- const { stdout, stderr } = await execFileAsync8(GIT2, ["push", "origin", refspec], {
93252
- cwd: submodulePath,
93253
- encoding: "utf8",
93254
- timeout: 3e4,
93255
- maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
93256
- windowsHide: true
93257
- });
93258
- return { stdout: String(stdout || ""), stderr: String(stderr || ""), refspec };
93259
- };
93260
- const importCommitFromWorktreeSubmodule = async (submodulePath, worktreeSubmodulePath, commit) => {
93261
- if (!fs52.existsSync(worktreeSubmodulePath)) return false;
93262
- try {
93263
- await runGit4(worktreeSubmodulePath, ["cat-file", "-e", `${commit}^{commit}`]);
93264
- } catch {
93265
- return false;
93266
- }
93267
- await runGit4(submodulePath, ["-c", "protocol.file.allow=always", "fetch", worktreeSubmodulePath, commit]);
93268
- await runGit4(submodulePath, ["cat-file", "-e", `${commit}^{commit}`]);
93269
- return true;
93270
- };
93271
- const findEquivalentPublishedCommit = async (submodulePath, commit, remoteRef) => {
93272
- try {
93273
- const targetTree = (await runGit4(submodulePath, ["rev-parse", `${commit}^{tree}`])).trim();
93274
- if (!targetTree) return void 0;
93275
- const candidates = (await runGit4(submodulePath, ["rev-list", "--max-count=100", remoteRef])).split("\n").map((s2) => s2.trim()).filter(Boolean);
93276
- for (const candidate of candidates) {
93277
- let tree = "";
93278
- try {
93279
- tree = (await runGit4(submodulePath, ["rev-parse", `${candidate}^{tree}`])).trim();
93280
- } catch {
93281
- continue;
93282
- }
93283
- if (tree && tree === targetTree) return candidate;
93284
- }
93285
- } catch {
93286
- }
93287
- return void 0;
93288
- };
93289
- const treeOutput = await runGit4(repoRoot, ["ls-tree", "-r", "-z", mergedTree]);
93290
- const gitlinks = treeOutput.split("\0").filter(Boolean).map((record2) => {
93291
- const match = /^160000\s+commit\s+([0-9a-f]{40})\t(.+)$/.exec(record2);
93292
- return match ? { commit: match[1], path: match[2] } : null;
93293
- }).filter((entry) => !!entry);
93294
- for (const gitlink of gitlinks) {
93295
- const submodulePath = (0, import_path19.resolve)(repoRoot, gitlink.path);
93296
- const entry = {
93297
- path: gitlink.path,
93298
- commit: gitlink.commit,
93299
- reachable: false
93300
- };
93301
- let submoduleDefaultBranch = "main";
93302
- try {
93303
- if (!fs52.existsSync(submodulePath)) {
93304
- entry.error = `Submodule checkout missing at ${gitlink.path}`;
93305
- entry.publishRequired = true;
93306
- if (options.allowAutoPublishSubmoduleMainCommits === true) {
93307
- entry.autoPublishAllowed = true;
93308
- entry.autoPublishAttempted = false;
93309
- entry.autoPublishSkippedReason = `submodule checkout missing at ${gitlink.path}; cannot perform non-force push to origin/main`;
93310
- }
93311
- entries.push(entry);
93312
- continue;
93313
- }
93314
- entry.checkedLocal = true;
93315
- try {
93316
- await runGit4(submodulePath, ["cat-file", "-e", `${gitlink.commit}^{commit}`]);
93317
- entry.localReachable = true;
93318
- } catch {
93319
- entry.localReachable = false;
93320
- if (options.allowAutoPublishSubmoduleMainCommits === true && options.worktreeRoot) {
93321
- try {
93322
- const imported = await importCommitFromWorktreeSubmodule(submodulePath, (0, import_path19.resolve)(options.worktreeRoot, gitlink.path), gitlink.commit);
93323
- if (imported) {
93324
- entry.localReachable = true;
93325
- entry.importedFromWorktree = true;
93326
- }
93327
- } catch (importError) {
93328
- entry.autoPublishSkippedReason = `candidate commit was not present in the source checkout and could not be imported from worktree submodule: ${truncateValidationOutput(importError?.stderr || importError?.message || String(importError))}`;
93329
- }
93330
- }
93331
- }
93332
- try {
93333
- entry.remote = "origin";
93334
- let remoteUrl = "";
93335
- try {
93336
- remoteUrl = (await runGit4(submodulePath, ["remote", "get-url", "origin"])).trim();
93337
- if (!remoteUrl) throw new Error("origin remote has no URL");
93338
- entry.remoteUrl = remoteUrl;
93339
- } catch {
93340
- entry.error = "Submodule remote reachability check failed: no configured origin remote";
93341
- entry.publishRequired = true;
93342
- if (options.allowAutoPublishSubmoduleMainCommits === true) {
93343
- entry.autoPublishAllowed = true;
93344
- entry.autoPublishAttempted = false;
93345
- entry.autoPublishSkippedReason = "submodule origin remote is not configured; cannot perform non-force push to origin/main";
93346
- }
93347
- entries.push(entry);
93348
- continue;
93349
- }
93350
- submoduleDefaultBranch = await resolveSubmoduleDefaultBranch({
93351
- submoduleRepoPath: submodulePath,
93352
- superprojectWorkspace: repoRoot,
93353
- submodulePath: gitlink.path
93354
- });
93355
- entry.remoteMainBranch = submoduleDefaultBranch;
93356
- const verdict = await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, submoduleDefaultBranch);
93357
- if (verdict.state === "contained") {
93358
- entry.fetchedFromOrigin = true;
93359
- entry.remoteReachable = true;
93360
- entry.remoteMainReachable = true;
93361
- entry.reachable = true;
93362
- } else if (verdict.state === "undeterminable") {
93363
- const e = verdict.error;
93364
- entry.remoteMainUndeterminable = true;
93365
- entry.publishRequired = false;
93366
- const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
93367
- entry.error = `Submodule remote main reachability for origin/${submoduleDefaultBranch} could not be determined (the remote was not successfully consulted): ${details}. This is not evidence that ${gitlink.commit} is unpublished \u2014 resolve the remote access problem and re-run; Refinery will not publish on an undetermined verdict.`;
93368
- if (options.allowAutoPublishSubmoduleMainCommits === true) {
93369
- entry.autoPublishAllowed = true;
93370
- entry.autoPublishAttempted = false;
93371
- entry.autoPublishSkippedReason = `remote main containment for origin/${submoduleDefaultBranch} could not be determined, so Refinery will not push \u2014 a failed fetch is not proof the commit is unpublished`;
93372
- }
93373
- } else {
93374
- const e = verdict.error;
93375
- entry.remoteReachable = false;
93376
- entry.remoteMainReachable = false;
93377
- const equivalentPublished = await findEquivalentPublishedCommit(submodulePath, gitlink.commit, `refs/remotes/origin/${submoduleDefaultBranch}`);
93378
- if (equivalentPublished) {
93379
- entry.equivalentPublishedCommit = equivalentPublished;
93380
- entry.publishRequired = false;
93381
- entry.error = `Submodule commit ${gitlink.commit} is not reachable from origin/${submoduleDefaultBranch}, but an equivalent commit ${equivalentPublished} (identical tree) is already published there; converge the gitlink to the published commit instead of publishing a same-content twin.`;
93382
- if (options.allowAutoPublishSubmoduleMainCommits === true) {
93383
- entry.autoPublishAllowed = true;
93384
- entry.autoPublishAttempted = false;
93385
- entry.autoPublishSkippedReason = `an equivalent commit (${equivalentPublished}) is already published on origin/${submoduleDefaultBranch}; publishing a same-content twin is wrong \u2014 converge the gitlink to the published commit instead`;
93386
- }
93387
- } else {
93388
- entry.publishRequired = true;
93389
- const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
93390
- entry.error = `Submodule remote main reachability check failed for origin/${submoduleDefaultBranch}: ${details}`;
93391
- if (options.allowAutoPublishSubmoduleMainCommits === true && entry.localReachable === true) {
93392
- entry.autoPublishAllowed = true;
93393
- entry.autoPublishAttempted = true;
93394
- try {
93395
- const publish = await publishCommitToRemoteMain(submodulePath, gitlink.commit, submoduleDefaultBranch);
93396
- entry.autoPublishRefspec = publish.refspec;
93397
- entry.publishStdout = truncateValidationOutput(publish.stdout);
93398
- entry.publishStderr = truncateValidationOutput(publish.stderr);
93399
- entry.autoPublishSucceeded = true;
93400
- const postPublish = await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, submoduleDefaultBranch);
93401
- if (postPublish.state !== "contained") {
93402
- entry.autoPublishVerified = false;
93403
- const postDetails = truncateValidationOutput(
93404
- postPublish.error?.stderr || postPublish.error?.message || String(postPublish.error)
93405
- );
93406
- if (postPublish.state === "undeterminable") entry.remoteMainUndeterminable = true;
93407
- else entry.remoteMainReachable = false;
93408
- entry.error = `Submodule auto-publish to origin/${submoduleDefaultBranch} reported success but post-publish verification did not confirm containment (${postPublish.state}): ${postDetails}`;
93409
- } else {
93410
- entry.fetchedFromOrigin = true;
93411
- entry.remoteReachable = true;
93412
- entry.remoteMainReachable = true;
93413
- entry.autoPublishVerified = true;
93414
- entry.publishRequired = false;
93415
- entry.reachable = true;
93416
- entry.error = void 0;
93417
- }
93418
- } catch (publishError) {
93419
- entry.autoPublishSucceeded = false;
93420
- entry.autoPublishVerified = false;
93421
- const publishDetails = truncateValidationOutput(publishError?.stderr || publishError?.message || String(publishError));
93422
- entry.error = `Submodule auto-publish to origin/${submoduleDefaultBranch} failed or could not be verified: ${publishDetails}`;
93423
- }
93424
- } else if (options.allowAutoPublishSubmoduleMainCommits === true) {
93425
- entry.autoPublishAllowed = true;
93426
- entry.autoPublishAttempted = false;
93427
- entry.autoPublishSkippedReason = entry.autoPublishSkippedReason || `candidate commit is not reachable in the source checkout or worktree submodule, so Refinery cannot push it to origin/${submoduleDefaultBranch}`;
93428
- }
93429
- }
93430
- }
93431
- } catch (e) {
93432
- entry.remoteReachable = false;
93433
- entry.remoteMainReachable = false;
93434
- entry.publishRequired = true;
93435
- const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
93436
- entry.error = `Submodule remote main reachability check failed for origin/${submoduleDefaultBranch}: ${details}`;
93437
- }
93438
- } catch (e) {
93439
- entry.error = truncateValidationOutput(e?.message || String(e));
93440
- entry.publishRequired = true;
93441
- }
93442
- entries.push(entry);
93443
- }
93444
- const unreachable = entries.filter((entry) => !entry.reachable);
93445
- return {
93446
- status: unreachable.length ? "failed" : "passed",
93447
- checked: entries.length,
93448
- unreachable: unreachable.map((entry) => ({ ...entry, publishRequired: entry.publishRequired !== false })),
93449
- entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
93450
- durationMs: Date.now() - startedAt,
93451
- autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
93452
- autoPublishPolicySource: options.autoPublishPolicySource
93453
- };
93454
- } catch (e) {
93455
- const unreachable = entries.filter((entry) => !entry.reachable).map((entry) => ({ ...entry, publishRequired: entry.publishRequired !== false }));
93456
- return {
93457
- status: "failed",
93458
- checked: entries.length,
93459
- unreachable,
93460
- entries: entries.map((entry) => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
93461
- durationMs: Date.now() - startedAt,
93462
- autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
93463
- autoPublishPolicySource: options.autoPublishPolicySource,
93464
- error: truncateValidationOutput(e?.message || String(e))
93465
- };
93466
- }
93467
- }
93468
94172
  function buildMeshRefineValidationPlan(mesh, workspace) {
93469
94173
  const plan = resolveMeshRefineValidationPlan(mesh, workspace);
93470
94174
  const mapCommand = (command) => ({
@@ -93749,9 +94453,6 @@ ${mergeTreeErr?.stderr || ""}`;
93749
94453
  var import_node_child_process11;
93750
94454
  var REFINE_VALIDATION_TIMEOUT_MS;
93751
94455
  var REFINE_VALIDATION_OUTPUT_LIMIT_BYTES;
93752
- var REFINE_VALIDATION_SUMMARY_HEAD_CHARS;
93753
- var REFINE_VALIDATION_SUMMARY_TAIL_CHARS;
93754
- var REFINE_VALIDATION_SUMMARY_CHARS;
93755
94456
  var REFINE_VALIDATION_LOG_DIR;
93756
94457
  var init_mesh_refine_gates = __esm2({
93757
94458
  "src/mesh/mesh-refine-gates.ts"() {
@@ -93769,9 +94470,6 @@ ${mergeTreeErr?.stderr || ""}`;
93769
94470
  init_mesh_refine_gitlink_utils();
93770
94471
  REFINE_VALIDATION_TIMEOUT_MS = 12e4;
93771
94472
  REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
93772
- REFINE_VALIDATION_SUMMARY_HEAD_CHARS = 2e3;
93773
- REFINE_VALIDATION_SUMMARY_TAIL_CHARS = 6e3;
93774
- REFINE_VALIDATION_SUMMARY_CHARS = REFINE_VALIDATION_SUMMARY_HEAD_CHARS + REFINE_VALIDATION_SUMMARY_TAIL_CHARS;
93775
94473
  REFINE_VALIDATION_LOG_DIR = (0, import_path19.join)(".adhdev", "logs");
93776
94474
  }
93777
94475
  });
@@ -97028,6 +97726,7 @@ ${e?.stderr || ""}`;
97028
97726
  ASSISTANT_JOURNAL_TOPIC: () => ASSISTANT_JOURNAL_TOPIC,
97029
97727
  AcpProviderInstance: () => AcpProviderInstance,
97030
97728
  AgentStreamPoller: () => AgentStreamPoller,
97729
+ BEACON_BOARD_TTL_MS: () => BEACON_BOARD_TTL_MS,
97031
97730
  BEACON_ENV: () => BEACON_ENV,
97032
97731
  BUILD_CHANNEL_ENV_VAR: () => BUILD_CHANNEL_ENV_VAR,
97033
97732
  BUILTIN_CHAT_MESSAGE_KINDS: () => BUILTIN_CHAT_MESSAGE_KINDS,
@@ -97093,6 +97792,7 @@ ${e?.stderr || ""}`;
97093
97792
  FLEET_STATUS_PARITY_INTERVAL_MS: () => FLEET_STATUS_PARITY_INTERVAL_MS,
97094
97793
  FLEET_STATUS_PARITY_SUMMARY_INTERVAL_MS: () => FLEET_STATUS_PARITY_SUMMARY_INTERVAL_MS,
97095
97794
  FLEET_STATUS_RING: () => FLEET_STATUS_RING,
97795
+ FLEET_STATUS_SUB_VIEW: () => FLEET_STATUS_SUB_VIEW,
97096
97796
  FLEET_STATUS_TOPIC: () => FLEET_STATUS_TOPIC,
97097
97797
  FsmDriver: () => FsmDriver,
97098
97798
  GOAL_PREVIEW_MAX: () => GOAL_PREVIEW_MAX,
@@ -97299,6 +97999,7 @@ ${e?.stderr || ""}`;
97299
97999
  commitMeshGraphPlan: () => commitMeshGraphPlan,
97300
98000
  compareGitSnapshots: () => compareGitSnapshots,
97301
98001
  compensateWorkspaceIntent: () => compensateWorkspaceIntent,
98002
+ computeBeaconDiagnostics: () => computeBeaconDiagnostics,
97302
98003
  computeMeshGraphPlanDigest: () => computeMeshGraphPlanDigest,
97303
98004
  computeMeshMissionStats: () => computeMeshMissionStats,
97304
98005
  computeMeshTaskStats: () => computeMeshTaskStats,
@@ -97320,6 +98021,7 @@ ${e?.stderr || ""}`;
97320
98021
  createDefaultMeshHostMetadata: () => createDefaultMeshHostMetadata,
97321
98022
  createFleetAuthority: () => createFleetAuthority,
97322
98023
  createFleetAuthorityIfConfigured: () => createFleetAuthorityIfConfigured,
98024
+ createFleetStatusPeerViewConsumer: () => createFleetStatusPeerViewConsumer,
97323
98025
  createGitCompactSummary: () => createGitCompactSummary,
97324
98026
  createGitSnapshotStore: () => createGitSnapshotStore,
97325
98027
  createGitWorkspaceMonitor: () => createGitWorkspaceMonitor2,
@@ -97598,6 +98300,7 @@ ${e?.stderr || ""}`;
97598
98300
  printQuotaRefreshOutcome: () => printQuotaRefreshOutcome2,
97599
98301
  probeCdpPort: () => probeCdpPort,
97600
98302
  projectBeaconReport: () => projectBeaconReport,
98303
+ projectFleetStatusEntry: () => projectFleetStatusEntry,
97601
98304
  projectGraphPublicPolicy: () => projectGraphPublicPolicy,
97602
98305
  projectMeshLedgerEntry: () => projectMeshLedgerEntry,
97603
98306
  pruneOperatingNotes: () => pruneOperatingNotes,
@@ -97615,6 +98318,7 @@ ${e?.stderr || ""}`;
97615
98318
  readCodexCliSession: () => readSession2,
97616
98319
  readDiskSpace: () => readDiskSpace,
97617
98320
  readHermesCliSession: () => readSession4,
98321
+ readKeyStaleAdvisory: () => readKeyStaleAdvisory,
97618
98322
  readLatestCodexRateLimits: () => readLatestCodexRateLimits,
97619
98323
  readLedgerEntries: () => readLedgerEntries,
97620
98324
  readLedgerSlice: () => readLedgerSlice,
@@ -97746,11 +98450,13 @@ ${e?.stderr || ""}`;
97746
98450
  summarizeMeshUsage: () => summarizeMeshUsage,
97747
98451
  summarizeMissionTasks: () => summarizeMissionTasks,
97748
98452
  summarizeSeqscribeStats: () => summarizeSeqscribeStats,
98453
+ summarizeSoleCopy: () => summarizeSoleCopy,
97749
98454
  supportsUnicodeSymbols: () => supportsUnicodeSymbols,
97750
98455
  sweepMeshGraphGateTimeouts: () => sweepMeshGraphGateTimeouts,
97751
98456
  taskDependenciesSatisfied: () => taskDependenciesSatisfied,
97752
98457
  taskIsParked: () => taskIsParked,
97753
98458
  terminalizeSiblingDispatch: () => terminalizeSiblingDispatch,
98459
+ toBeaconDiagnosticsSummary: () => toBeaconDiagnosticsSummary,
97754
98460
  toJsonValue: () => toJsonValue,
97755
98461
  tombstoneOperatingNote: () => tombstoneOperatingNote,
97756
98462
  totalTokens: () => totalTokens,
@@ -98826,1066 +99532,421 @@ ${e?.stderr || ""}`;
98826
99532
  if (popResult !== void 0 && "success" in popResult) return popResult;
98827
99533
  return { success: true, stashPopped: true };
98828
99534
  }
98829
- case "git_checkout_files": {
98830
- if (!services.checkoutFiles) return serviceNotImplemented(command);
98831
- const paths = args?.paths;
98832
- if (!Array.isArray(paths) || paths.length === 0) {
98833
- return failure("invalid_args", "paths must be a non-empty array");
98834
- }
98835
- if (paths.length > 50) {
98836
- return failure("invalid_args", "paths array exceeds maximum of 50 entries");
98837
- }
98838
- const checkoutResult = await runService(() => services.checkoutFiles({ workspace, paths }));
98839
- return "success" in checkoutResult ? checkoutResult : { success: true, checkedOut: checkoutResult.checkedOut };
98840
- }
98841
- case "git_remote_url": {
98842
- if (!services.getRemoteUrl) return serviceNotImplemented(command);
98843
- const remote = typeof args?.remote === "string" && args.remote.trim() ? args.remote.trim() : "origin";
98844
- const remoteResult = await runService(() => services.getRemoteUrl({ workspace, remote }));
98845
- if ("success" in remoteResult) return remoteResult;
98846
- return { success: true, remoteUrl: remoteResult.remoteUrl, remote: remoteResult.remote };
98847
- }
98848
- case "git_push": {
98849
- if (!services.push) return serviceNotImplemented(command);
98850
- const remote = typeof args?.remote === "string" && args.remote.trim() ? args.remote.trim() : "origin";
98851
- const branch = typeof args?.branch === "string" && args.branch.trim() ? args.branch.trim() : void 0;
98852
- const setUpstream = Boolean(args?.setUpstream);
98853
- if (!/^[a-zA-Z0-9_.\-]+$/.test(remote)) {
98854
- return failure("invalid_args", "remote must contain only alphanumeric characters, dots, hyphens, and underscores");
98855
- }
98856
- if (branch !== void 0 && !/^[a-zA-Z0-9/_.\-]+$/.test(branch)) {
98857
- return failure("invalid_args", "branch must contain only alphanumeric characters, slashes, dots, hyphens, and underscores");
98858
- }
98859
- const pushResult = await runService(() => services.push({ workspace, remote, branch, setUpstream }));
98860
- return "success" in pushResult ? pushResult : { success: true, push: pushResult };
98861
- }
98862
- default:
98863
- return failure("invalid_args", `Unknown Git command: ${command}`);
98864
- }
98865
- }
98866
- function validateMutatingMessage(value) {
98867
- if (typeof value !== "string" || !value.trim()) {
98868
- return failure("invalid_args", "message must be a non-empty string");
98869
- }
98870
- const msg = value.trim();
98871
- if (msg.length > 200) {
98872
- return failure("invalid_args", "message must be 200 characters or fewer");
98873
- }
98874
- return msg;
98875
- }
98876
- async function gitCheckpoint(workspace, message, includeUntracked) {
98877
- const repo = await resolveGitRepository(workspace);
98878
- const repoRoot = repo.repoRoot;
98879
- const statusResult = await getGitRepoStatus(workspace, { forceFresh: true });
98880
- if (statusResult.hasConflicts) {
98881
- throw new GitCommandError("conflict", "Repository has conflicts \u2014 resolve before checkpointing");
98882
- }
98883
- const dirtySubmodules = (statusResult.submodules || []).filter((submodule) => submodule.dirty);
98884
- if (dirtySubmodules.length > 0) {
98885
- const paths = dirtySubmodules.map((submodule) => submodule.path).join(", ");
98886
- throw new GitCommandError(
98887
- "dirty_index_required",
98888
- `Repository has dirty submodules that must be checkpointed first: ${paths}. Checkpoint or commit each dirty submodule, then checkpoint this repository to record gitlink changes.`
98889
- );
98890
- }
98891
- const addArgs = includeUntracked ? ["-A"] : ["-u"];
98892
- await runGit(repo, ["add", ...addArgs], { cwd: repoRoot });
98893
- const fullMsg = `adhdev: checkpoint ${message}`;
98894
- let commitSha;
98895
- try {
98896
- await runGit(repo, ["commit", "-m", fullMsg], { cwd: repoRoot });
98897
- const revResult = await runGit(repo, ["rev-parse", "HEAD"], { cwd: repoRoot });
98898
- commitSha = revResult.stdout.trim();
98899
- } catch (err) {
98900
- const output = (err?.stdout || "") + (err?.stderr || "");
98901
- if (/nothing to commit/i.test(output)) {
98902
- return {
98903
- workspace: repo.workspace,
98904
- repoRoot,
98905
- isGitRepo: true,
98906
- message: fullMsg,
98907
- status: "skipped",
98908
- skipped: true,
98909
- noop: true,
98910
- reason: "nothing_to_commit",
98911
- lastCheckedAt: Date.now()
98912
- };
98913
- }
98914
- throw err;
98915
- }
98916
- return {
98917
- workspace: repo.workspace,
98918
- repoRoot,
98919
- isGitRepo: true,
98920
- commit: commitSha,
98921
- message: fullMsg,
98922
- status: "created",
98923
- lastCheckedAt: Date.now()
98924
- };
98925
- }
98926
- async function gitStashPush(workspace, message, includeUntracked) {
98927
- const repo = await resolveGitRepository(workspace);
98928
- const repoRoot = repo.repoRoot;
98929
- const stashArgs = ["stash", "push", "-m", message];
98930
- if (includeUntracked) stashArgs.push("--include-untracked");
98931
- const result = await runGit(repo, stashArgs, { cwd: repoRoot });
98932
- if (/No local changes to save/i.test(result.stdout + result.stderr)) {
98933
- throw new GitCommandError("git_command_failed", "Nothing to stash");
98934
- }
98935
- return {
98936
- workspace: repo.workspace,
98937
- repoRoot,
98938
- isGitRepo: true,
98939
- stashRef: "stash@{0}",
98940
- message,
98941
- lastCheckedAt: Date.now()
98942
- };
98943
- }
98944
- async function gitStashPop(workspace, stashRef) {
98945
- const repo = await resolveGitRepository(workspace);
98946
- const repoRoot = repo.repoRoot;
98947
- const popArgs = stashRef ? ["stash", "pop", stashRef] : ["stash", "pop"];
98948
- await runGit(repo, popArgs, { cwd: repoRoot });
98949
- }
98950
- async function gitCheckoutFiles(workspace, paths) {
98951
- const repo = await resolveGitRepository(workspace);
98952
- const repoRoot = repo.repoRoot;
98953
- const normalizedPaths = [];
98954
- for (const p of paths) {
98955
- if (typeof p !== "string" || !p.trim() || p.includes("\0")) {
98956
- throw new GitCommandError("invalid_args", `Invalid path: ${String(p)}`);
98957
- }
98958
- if (path15.isAbsolute(p)) {
98959
- throw new GitCommandError("invalid_args", `Path must be repository-relative, not absolute: ${p}`);
98960
- }
98961
- const normalized = path15.normalize(p.trim()).split(path15.sep).join("/");
98962
- if (normalized.startsWith("../") || normalized === "..") {
98963
- throw new GitCommandError("path_outside_repo", `Path is outside repository root: ${p}`);
98964
- }
98965
- const absolutePath = path15.resolve(repoRoot, normalized);
98966
- if (!isPathInside(repoRoot, absolutePath)) {
98967
- throw new GitCommandError("path_outside_repo", `Path is outside repository root: ${p}`);
98968
- }
98969
- normalizedPaths.push(normalized);
98970
- }
98971
- await runGit(repo, ["checkout", "--", ...normalizedPaths], { cwd: repoRoot });
98972
- return { checkedOut: normalizedPaths };
98973
- }
98974
- async function gitGetRemoteUrl(workspace, remote) {
98975
- const repo = await resolveGitRepository(workspace);
98976
- const result = await runGit(repo, ["remote", "get-url", remote], { cwd: repo.repoRoot });
98977
- const remoteUrl = result.stdout.trim();
98978
- if (!remoteUrl) {
98979
- throw new GitCommandError("git_command_failed", `Remote '${remote}' has no URL`);
98980
- }
98981
- return { remoteUrl, remote };
98982
- }
98983
- async function gitPush(workspace, remote, branch, setUpstream) {
98984
- const lastCheckedAt = Date.now();
98985
- const repo = await resolveGitRepository(workspace);
98986
- const repoRoot = repo.repoRoot;
98987
- let resolvedBranch = branch;
98988
- if (!resolvedBranch) {
98989
- const branchResult = await runGit(repo, ["symbolic-ref", "--short", "HEAD"], { cwd: repoRoot });
98990
- resolvedBranch = branchResult.stdout.trim();
98991
- if (!resolvedBranch) {
98992
- throw new GitCommandError("git_command_failed", "Cannot push: not on a branch (detached HEAD)");
98993
- }
98994
- }
98995
- const pushArgs = ["push"];
98996
- if (setUpstream) pushArgs.push("--set-upstream");
98997
- pushArgs.push(remote, resolvedBranch);
98998
- let output = "";
98999
- let newBranch = false;
99000
- try {
99001
- const result = await runGit(repo, pushArgs, { cwd: repoRoot });
99002
- output = (result.stdout + result.stderr).trim();
99003
- newBranch = /\[new branch\]/i.test(output);
99004
- } catch (err) {
99005
- const errOutput = (err?.stdout ?? "") + (err?.stderr ?? "");
99006
- if (!setUpstream && /no upstream branch|set-upstream/i.test(errOutput)) {
99007
- const retryArgs = ["push", "--set-upstream", remote, resolvedBranch];
99008
- const retryResult = await runGit(repo, retryArgs, { cwd: repoRoot });
99009
- output = (retryResult.stdout + retryResult.stderr).trim();
99010
- newBranch = /\[new branch\]/i.test(output);
99011
- } else {
99012
- throw new GitCommandError("git_command_failed", errOutput || err?.message || "git push failed");
99013
- }
99014
- }
99015
- return {
99016
- workspace: repo.workspace,
99017
- repoRoot,
99018
- isGitRepo: true,
99019
- remote,
99020
- branch: resolvedBranch,
99021
- output,
99022
- newBranch,
99023
- lastCheckedAt
99024
- };
99025
- }
99026
- function formatOptionalGitLogRangeArg(flag, value) {
99027
- return value ? [`${flag}=${value}`] : [];
99028
- }
99029
- async function getGitLog(workspace, options) {
99030
- const lastCheckedAt = Date.now();
99031
- const repo = await resolveGitRepository(workspace);
99032
- const repoRoot = repo.repoRoot;
99033
- const boundedLimit = Math.max(1, Math.min(200, Math.floor(options.limit || 50)));
99034
- const selectedPath = options.path ? validateGitLogPath(repoRoot, options.path) : void 0;
99035
- const result = await runGit(
99036
- repo,
99037
- [
99038
- "log",
99039
- `--max-count=${boundedLimit}`,
99040
- "--format=%H%x00%an%x00%ae%x00%at%x00%ct%x00%s",
99041
- ...formatOptionalGitLogRangeArg("--since", options.since),
99042
- ...formatOptionalGitLogRangeArg("--until", options.until),
99043
- "--",
99044
- ...selectedPath ? [selectedPath] : []
99045
- ],
99046
- { cwd: repoRoot }
99047
- );
99048
- const entries = result.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => {
99049
- const [commit = "", authorName, authorEmail, authoredAt, committedAt, ...messageParts] = line.split("\0");
99050
- return {
99051
- commit,
99052
- message: messageParts.join("\0"),
99053
- authorName: authorName || void 0,
99054
- authorEmail: authorEmail || void 0,
99055
- authoredAt: authoredAt ? Number.parseInt(authoredAt, 10) * 1e3 : void 0,
99056
- committedAt: committedAt ? Number.parseInt(committedAt, 10) * 1e3 : void 0
99057
- };
99058
- }).filter((entry) => entry.commit.length > 0);
99059
- return {
99060
- workspace: repo.workspace,
99061
- repoRoot,
99062
- isGitRepo: true,
99063
- entries,
99064
- limit: boundedLimit,
99065
- truncated: entries.length >= boundedLimit,
99066
- lastCheckedAt
99067
- };
99068
- }
99069
- function validateGitLogPath(repoRoot, filePath) {
99070
- if (!filePath.trim() || filePath.includes("\0")) {
99071
- throw new GitCommandError("invalid_args", "path must be a non-empty repository-relative path");
99072
- }
99073
- if (path15.isAbsolute(filePath)) {
99074
- throw new GitCommandError("invalid_args", "path must be repository-relative");
99075
- }
99076
- const normalized = path15.normalize(filePath).split(path15.sep).join("/");
99077
- const absolutePath = path15.resolve(repoRoot, normalized);
99078
- if (!isPathInside(repoRoot, absolutePath) || normalized.startsWith("../") || normalized === "..") {
99079
- throw new GitCommandError("path_outside_repo", "Git log path is outside the repository root");
99080
- }
99081
- return normalized;
99082
- }
99083
- var BUSY_STATUSES = /* @__PURE__ */ new Set(["streaming", "waiting_approval"]);
99084
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["idle", "error"]);
99085
- var TurnSnapshotTracker = class {
99086
- lastStatus = /* @__PURE__ */ new Map();
99087
- onTurnCompleted;
99088
- constructor(onTurnCompleted) {
99089
- this.onTurnCompleted = onTurnCompleted;
99090
- }
99091
- record(sessionId, status, workspace) {
99092
- const prev = this.lastStatus.get(sessionId);
99093
- this.lastStatus.set(sessionId, status);
99094
- if (workspace && prev && BUSY_STATUSES.has(prev) && TERMINAL_STATUSES.has(status)) {
99095
- this.onTurnCompleted({ sessionId, workspace });
99096
- }
99097
- }
99098
- forget(sessionId) {
99099
- this.lastStatus.delete(sessionId);
99100
- }
99101
- };
99102
- init_git_worktree();
99103
- init_config();
99104
- init_config_dir();
99105
- init_embedded_path_health();
99106
- var os11 = __toESM2(require("os"));
99107
- var path17 = __toESM2(require("path"));
99108
- var import_session_host_core22 = require_dist();
99109
- init_config_dir();
99110
- init_track_identity();
99111
- var DEFAULT_SESSION_HOST_APP_NAME = IDENTITY.sessionHostName;
99112
- var RESERVED_GLOBAL_SESSION_HOST_APP_NAME = IDENTITY.sessionHostName;
99113
- var DEFAULT_STANDALONE_SESSION_HOST_APP_NAME = "adhdev-standalone";
99114
- function getReservedStandaloneNamespaceWarning() {
99115
- return `Standalone session-host namespace '${RESERVED_GLOBAL_SESSION_HOST_APP_NAME}' is reserved for the global daemon. Falling back to '${DEFAULT_STANDALONE_SESSION_HOST_APP_NAME}' for this standalone run.`;
99116
- }
99117
- function resolveSessionHostAppNameResolution2(options = {}) {
99118
- const env2 = options.env || process.env;
99119
- const explicit = typeof env2.ADHDEV_SESSION_HOST_NAME === "string" ? env2.ADHDEV_SESSION_HOST_NAME.trim() : "";
99120
- if (explicit) {
99121
- if (options.standalone && explicit === RESERVED_GLOBAL_SESSION_HOST_APP_NAME) {
99122
- return {
99123
- appName: DEFAULT_STANDALONE_SESSION_HOST_APP_NAME,
99124
- warning: getReservedStandaloneNamespaceWarning(),
99125
- source: "reserved-standalone-fallback"
99126
- };
99127
- }
99128
- return {
99129
- appName: explicit,
99130
- source: "explicit"
99131
- };
99132
- }
99133
- return {
99134
- appName: options.standalone ? DEFAULT_STANDALONE_SESSION_HOST_APP_NAME : DEFAULT_SESSION_HOST_APP_NAME,
99135
- source: "default"
99136
- };
99137
- }
99138
- function resolveSessionHostAppName(options = {}) {
99139
- return resolveSessionHostAppNameResolution2(options).appName;
99140
- }
99141
- var InstanceContextConflictError = class extends Error {
99142
- constructor(message) {
99143
- super(message);
99144
- this.name = "InstanceContextConflictError";
99145
- }
99146
- };
99147
- var cached22 = null;
99148
- function resolveInstanceContext(options = {}) {
99149
- const env2 = options.env ?? process.env;
99150
- const homeDir2 = options.homeDir ?? os11.homedir();
99151
- const envDir = typeof env2.ADHDEV_CONFIG_DIR === "string" ? env2.ADHDEV_CONFIG_DIR.trim() : "";
99152
- const explicitDir = typeof options.configDir === "string" ? options.configDir.trim() : "";
99153
- if (explicitDir && envDir && (0, import_session_host_core22.canonicalizeInstancePath)(explicitDir) !== (0, import_session_host_core22.canonicalizeInstancePath)(envDir)) {
99154
- throw new InstanceContextConflictError(
99155
- `Conflicting instance identity: explicit configDir "${explicitDir}" vs ADHDEV_CONFIG_DIR "${envDir}". Refusing to merge mutable namespaces \u2014 fix the caller or unset one of the two.`
99156
- );
99157
- }
99158
- const configDir = explicitDir || resolveConfigDir(env2, homeDir2);
99159
- const trimmed = configDir.replace(/[\\/]+$/, "");
99160
- return {
99161
- configDir,
99162
- canonicalConfigDir: (0, import_session_host_core22.canonicalizeInstancePath)(configDir),
99163
- instanceDir: path17.basename(trimmed) || ".adhdev",
99164
- isDefault: (0, import_session_host_core22.isDefaultInstanceConfigDir)(configDir, homeDir2),
99165
- ipcKey: (0, import_session_host_core22.resolveSessionHostIpcKey)(configDir, homeDir2),
99166
- sessionHostAppName: resolveSessionHostAppName({ standalone: options.standalone, env: env2 })
99167
- };
99168
- }
99169
- function getProcessInstanceContext(options = {}) {
99170
- const envDir = typeof process.env.ADHDEV_CONFIG_DIR === "string" ? process.env.ADHDEV_CONFIG_DIR.trim() : "";
99171
- const key2 = `${envDir}|${os11.homedir()}|${options.standalone ? "standalone" : "daemon"}`;
99172
- if (!cached22 || cached22.key !== key2) {
99173
- cached22 = { key: key2, context: resolveInstanceContext({ standalone: options.standalone }) };
99174
- }
99175
- return cached22.context;
99176
- }
99177
- function resetProcessInstanceContextForTests() {
99178
- cached22 = null;
99179
- }
99180
- init_workspaces();
99181
- init_recent_activity();
99182
- var path20 = __toESM2(require("path"));
99183
- init_workspaces();
99184
- init_summary_metadata();
99185
- var MAX_SAVED_SESSIONS = 500;
99186
- function normalizeWorkspace2(workspace) {
99187
- if (!workspace) return "";
99188
- try {
99189
- return path20.resolve(expandPath(workspace));
99190
- } catch {
99191
- return path20.resolve(workspace);
99192
- }
99193
- }
99194
- function buildSavedProviderSessionKey(providerSessionId) {
99195
- return `saved:${providerSessionId.trim()}`;
99196
- }
99197
- function upsertSavedProviderSession(state, entry) {
99198
- const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
99199
- if (!providerSessionId) return state;
99200
- const id = buildSavedProviderSessionKey(providerSessionId);
99201
- const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
99202
- const nextEntry = {
99203
- id,
99204
- kind: entry.kind,
99205
- providerType: entry.providerType,
99206
- providerName: entry.providerName,
99207
- providerSessionId,
99208
- workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
99209
- summaryMetadata: normalizePersistedSummaryMetadata({
99210
- summaryMetadata: entry.summaryMetadata
99211
- }),
99212
- title: entry.title,
99213
- createdAt: existing?.createdAt || entry.createdAt || Date.now(),
99214
- lastUsedAt: entry.lastUsedAt || Date.now()
99215
- };
99216
- const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
99217
- return {
99218
- ...state,
99219
- savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
99220
- };
99221
- }
99222
- function getSavedProviderSessions(state, filters) {
99223
- return [...state.savedProviderSessions || []].filter((entry) => {
99224
- if (filters?.providerType && entry.providerType !== filters.providerType) return false;
99225
- if (filters?.kind && entry.kind !== filters.kind) return false;
99226
- return true;
99227
- }).map((entry) => ({
99228
- ...entry,
99229
- summaryMetadata: normalizePersistedSummaryMetadata({
99230
- summaryMetadata: entry.summaryMetadata
99231
- })
99232
- })).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
99233
- }
99234
- init_mesh_config();
99235
- init_dist();
99236
- init_dist();
99237
- init_dist();
99238
- init_dist();
99239
- init_coordinator_prompt();
99240
- init_mesh_missions();
99241
- init_mesh_task_stats();
99242
- init_mesh_review_inbox();
99243
- var import_node_child_process5 = require("child_process");
99244
- var import_promises5 = require("fs/promises");
99245
- var import_node_path2 = require("path");
99246
- var import_node_util4 = require("util");
99247
- init_git_locale();
99248
- init_repo_mesh_types();
99249
- init_mesh_config();
99250
- var import_fs13 = require("fs");
99251
- var import_path11 = require("path");
99252
- init_refine_config();
99253
- init_worktree_bootstrap_config();
99254
- init_change_impact_config();
99255
- init_mesh_config();
99256
- var MESH_INIT_REFINE_CONFIG_PATH = MESH_REFINE_CONFIG_LOCATIONS[0];
99257
- var MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH = MESH_WORKTREE_BOOTSTRAP_CONFIG_LOCATIONS[0];
99258
- var MESH_INIT_CHANGE_IMPACT_CONFIG_PATH = CHANGE_IMPACT_CONFIG_LOCATIONS[0];
99259
- var CANDIDATE_STALE_INPUTS = [
99260
- "package-lock.json",
99261
- "pnpm-lock.yaml",
99262
- "yarn.lock",
99263
- "bun.lockb",
99264
- "Cargo.lock",
99265
- "go.sum",
99266
- "poetry.lock",
99267
- "requirements.txt"
99268
- ];
99269
- function writeConfigFile(workspace, relativePath, config2) {
99270
- const target = (0, import_path11.join)(workspace, relativePath);
99271
- (0, import_fs13.mkdirSync)((0, import_path11.dirname)(target), { recursive: true });
99272
- (0, import_fs13.writeFileSync)(target, `${JSON.stringify(config2, null, 2)}
99273
- `, "utf-8");
99274
- return target;
99275
- }
99276
- function suggestMeshWorktreeBootstrapConfig(workspace) {
99277
- const commands = [];
99278
- const hasPackageJson = (0, import_fs13.existsSync)((0, import_path11.join)(workspace, "package.json"));
99279
- const hasNpmLock = (0, import_fs13.existsSync)((0, import_path11.join)(workspace, "package-lock.json"));
99280
- if (hasPackageJson) {
99281
- commands.push(
99282
- hasNpmLock ? { command: "npm", args: ["ci"] } : { command: "npm", args: ["install"] }
99283
- );
99284
- }
99285
- const staleInputs = CANDIDATE_STALE_INPUTS.filter((relative8) => (0, import_fs13.existsSync)((0, import_path11.join)(workspace, relative8)));
99286
- if (!commands.length) {
99287
- return { commands, staleInputs };
99288
- }
99289
- return {
99290
- commands,
99291
- staleInputs,
99292
- suggestedConfig: {
99293
- version: 1,
99294
- enabled: true,
99295
- runOnClone: true,
99296
- required: false,
99297
- commands,
99298
- ...staleInputs.length ? { staleInputs } : {}
99299
- }
99300
- };
99301
- }
99302
- var PROVIDER_PRIORITY_PREFERENCE = ["claude-cli", "codex-cli", "gemini-cli"];
99303
- function suggestNodeProviderPriority(detected) {
99304
- const installed = detected.filter((cli) => cli.installed);
99305
- const installedIds = installed.map((cli) => cli.id);
99306
- const preferred = PROVIDER_PRIORITY_PREFERENCE.filter((id) => installedIds.includes(id));
99307
- const rest = installedIds.filter((id) => !preferred.includes(id));
99308
- const providerPriority = [...preferred, ...rest];
99309
- return {
99310
- providerPriority,
99311
- installedProviders: installed.map((cli) => ({
99312
- id: cli.id,
99313
- displayName: cli.displayName,
99314
- ...cli.version ? { version: cli.version } : {}
99315
- }))
99316
- };
99317
- }
99318
- function runMeshInit(mesh, workspace, detected, options = {}) {
99319
- const write = options.write === true;
99320
- const overwrite = options.overwrite === true;
99321
- const refine2 = applyConfigSuggestion({
99322
- workspace,
99323
- relativePath: MESH_INIT_REFINE_CONFIG_PATH,
99324
- existing: loadMeshRefineConfig(mesh, workspace).config,
99325
- suggestedConfig: suggestMeshRefineConfig(mesh, workspace).suggestedConfig,
99326
- validate: (config2) => validateMeshRefineConfig(config2, MESH_INIT_REFINE_CONFIG_PATH).valid,
99327
- write,
99328
- overwrite
99329
- });
99330
- const bootstrapSuggestion = suggestMeshWorktreeBootstrapConfig(workspace);
99331
- const worktreeBootstrap = applyConfigSuggestion({
99332
- workspace,
99333
- relativePath: MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH,
99334
- existing: loadMeshWorktreeBootstrapConfig(mesh, workspace).config,
99335
- suggestedConfig: bootstrapSuggestion.suggestedConfig,
99336
- validate: (config2) => validateMeshWorktreeBootstrapConfig(config2, MESH_INIT_WORKTREE_BOOTSTRAP_CONFIG_PATH).valid,
99337
- write,
99338
- overwrite
99339
- });
99340
- const changeImpactLoaded = loadChangeImpactConfig(workspace);
99341
- const changeImpact = applyConfigSuggestion({
99342
- workspace,
99343
- relativePath: MESH_INIT_CHANGE_IMPACT_CONFIG_PATH,
99344
- existing: changeImpactLoaded.sourceType === "repo_file" ? changeImpactLoaded.config : void 0,
99345
- suggestedConfig: suggestChangeImpactConfig(workspace).suggestedConfig,
99346
- validate: (config2) => validateChangeImpactConfig(config2, MESH_INIT_CHANGE_IMPACT_CONFIG_PATH).valid,
99347
- write,
99348
- overwrite
99349
- });
99350
- const providers = suggestNodeProviderPriority(detected);
99351
- const refineLoaded = loadMeshRefineConfig(mesh, workspace);
99352
- const bootstrapLoaded = loadMeshWorktreeBootstrapConfig(mesh, workspace);
99353
- let magiKindPanels = {};
99354
- try {
99355
- magiKindPanels = listMagiKindPanelsReadOnly(typeof mesh?.id === "string" ? mesh.id : void 0);
99356
- } catch {
99357
- magiKindPanels = {};
99535
+ case "git_checkout_files": {
99536
+ if (!services.checkoutFiles) return serviceNotImplemented(command);
99537
+ const paths = args?.paths;
99538
+ if (!Array.isArray(paths) || paths.length === 0) {
99539
+ return failure("invalid_args", "paths must be a non-empty array");
99540
+ }
99541
+ if (paths.length > 50) {
99542
+ return failure("invalid_args", "paths array exceeds maximum of 50 entries");
99543
+ }
99544
+ const checkoutResult = await runService(() => services.checkoutFiles({ workspace, paths }));
99545
+ return "success" in checkoutResult ? checkoutResult : { success: true, checkedOut: checkoutResult.checkedOut };
99546
+ }
99547
+ case "git_remote_url": {
99548
+ if (!services.getRemoteUrl) return serviceNotImplemented(command);
99549
+ const remote = typeof args?.remote === "string" && args.remote.trim() ? args.remote.trim() : "origin";
99550
+ const remoteResult = await runService(() => services.getRemoteUrl({ workspace, remote }));
99551
+ if ("success" in remoteResult) return remoteResult;
99552
+ return { success: true, remoteUrl: remoteResult.remoteUrl, remote: remoteResult.remote };
99553
+ }
99554
+ case "git_push": {
99555
+ if (!services.push) return serviceNotImplemented(command);
99556
+ const remote = typeof args?.remote === "string" && args.remote.trim() ? args.remote.trim() : "origin";
99557
+ const branch = typeof args?.branch === "string" && args.branch.trim() ? args.branch.trim() : void 0;
99558
+ const setUpstream = Boolean(args?.setUpstream);
99559
+ if (!/^[a-zA-Z0-9_.\-]+$/.test(remote)) {
99560
+ return failure("invalid_args", "remote must contain only alphanumeric characters, dots, hyphens, and underscores");
99561
+ }
99562
+ if (branch !== void 0 && !/^[a-zA-Z0-9/_.\-]+$/.test(branch)) {
99563
+ return failure("invalid_args", "branch must contain only alphanumeric characters, slashes, dots, hyphens, and underscores");
99564
+ }
99565
+ const pushResult = await runService(() => services.push({ workspace, remote, branch, setUpstream }));
99566
+ return "success" in pushResult ? pushResult : { success: true, push: pushResult };
99567
+ }
99568
+ default:
99569
+ return failure("invalid_args", `Unknown Git command: ${command}`);
99358
99570
  }
99359
- const currentConfig2 = {
99360
- refine: refineLoaded.config,
99361
- worktreeBootstrap: bootstrapLoaded.config,
99362
- changeImpact: changeImpactLoaded.sourceType === "repo_file" ? changeImpactLoaded.config : void 0,
99363
- sourceTypes: {
99364
- refine: refineLoaded.sourceType,
99365
- worktreeBootstrap: bootstrapLoaded.sourceType,
99366
- changeImpact: changeImpactLoaded.sourceType
99367
- },
99368
- magiKindPanels
99369
- };
99370
- return {
99371
- success: true,
99372
- workspace,
99373
- dryRun: !write,
99374
- refine: refine2,
99375
- worktreeBootstrap,
99376
- changeImpact,
99377
- providers,
99378
- currentConfig: currentConfig2,
99379
- note: write ? "Configs written to disk are the execution source of truth; suggestions are scaffold and only take effect once saved. providerPriority is a recommendation \u2014 apply it to node policy via clone/policy update. currentConfig echoes what was on disk before this run." : "Dry-run: no files written. Re-run with write=true to persist the suggested configs. Heuristic suggestions never execute until saved as repo config. Use currentConfig to diff current-vs-suggested before overwriting."
99380
- };
99381
99571
  }
99382
- function applyConfigSuggestion(input) {
99383
- const { workspace, relativePath, existing, suggestedConfig, validate, write, overwrite } = input;
99384
- const absolute = (0, import_path11.join)(workspace, relativePath);
99385
- if (existing !== void 0 && !overwrite) {
99386
- return { path: absolute, relativePath, written: false, skippedReason: "already_exists", config: existing };
99387
- }
99388
- if (!suggestedConfig || !validate(suggestedConfig)) {
99389
- return { path: absolute, relativePath, written: false, skippedReason: "no_suggestion" };
99572
+ function validateMutatingMessage(value) {
99573
+ if (typeof value !== "string" || !value.trim()) {
99574
+ return failure("invalid_args", "message must be a non-empty string");
99390
99575
  }
99391
- if (!write) {
99392
- return { path: absolute, relativePath, written: false, config: suggestedConfig };
99576
+ const msg = value.trim();
99577
+ if (msg.length > 200) {
99578
+ return failure("invalid_args", "message must be 200 characters or fewer");
99393
99579
  }
99394
- const writtenPath = writeConfigFile(workspace, relativePath, suggestedConfig);
99395
- return { path: writtenPath, relativePath, written: true, config: suggestedConfig };
99580
+ return msg;
99396
99581
  }
99397
- init_git_worktree();
99398
- var execFileAsync3 = (0, import_node_util4.promisify)(import_node_child_process5.execFile);
99399
- var GIT_TIMEOUT_MS2 = 15e3;
99400
- async function git(cwd, args, allowFailure = false) {
99401
- try {
99402
- const { stdout } = await execFileAsync3("git", args, {
99403
- cwd,
99404
- encoding: "utf8",
99405
- timeout: GIT_TIMEOUT_MS2,
99406
- maxBuffer: 4 * 1024 * 1024,
99407
- windowsHide: true,
99408
- env: gitChildEnv()
99409
- });
99410
- return (stdout || "").trim();
99411
- } catch (error48) {
99412
- if (allowFailure) return "";
99413
- const stderr = typeof error48?.stderr === "string" ? error48.stderr.trim() : "";
99414
- throw new Error(stderr || error48?.message || `git ${args[0]} failed`);
99582
+ async function gitCheckpoint(workspace, message, includeUntracked) {
99583
+ const repo = await resolveGitRepository(workspace);
99584
+ const repoRoot = repo.repoRoot;
99585
+ const statusResult = await getGitRepoStatus(workspace, { forceFresh: true });
99586
+ if (statusResult.hasConflicts) {
99587
+ throw new GitCommandError("conflict", "Repository has conflicts \u2014 resolve before checkpointing");
99415
99588
  }
99416
- }
99417
- async function canonicalPath(path63) {
99418
- const absolute = (0, import_node_path2.resolve)(path63);
99419
- try {
99420
- return await (0, import_promises5.realpath)(absolute);
99421
- } catch {
99422
- return absolute;
99589
+ const dirtySubmodules = (statusResult.submodules || []).filter((submodule) => submodule.dirty);
99590
+ if (dirtySubmodules.length > 0) {
99591
+ const paths = dirtySubmodules.map((submodule) => submodule.path).join(", ");
99592
+ throw new GitCommandError(
99593
+ "dirty_index_required",
99594
+ `Repository has dirty submodules that must be checkpointed first: ${paths}. Checkpoint or commit each dirty submodule, then checkpoint this repository to record gitlink changes.`
99595
+ );
99423
99596
  }
99424
- }
99425
- function pathInside(child, parent) {
99426
- const rel = (0, import_node_path2.relative)(parent, child);
99427
- return rel !== "" && rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && !(0, import_node_path2.isAbsolute)(rel);
99428
- }
99429
- function failure2(code, error48, action, extra = {}) {
99430
- return { success: false, dryRun: true, code, error: error48, action, ...extra };
99431
- }
99432
- function isPlanFailure(value) {
99433
- return value.success === false;
99434
- }
99435
- function parseStatus(raw) {
99436
- const lines = raw.split(/\r?\n/).filter((line) => line && !line.startsWith("# "));
99437
- const conflictFiles = [];
99438
- for (const line of lines) {
99439
- if (line.startsWith("u ")) {
99440
- const fields = line.split(" ");
99441
- conflictFiles.push(fields.slice(10).join(" ") || fields.at(-1) || "(unknown)");
99442
- continue;
99443
- }
99444
- if (line.startsWith("1 ") || line.startsWith("2 ")) {
99445
- const xy = line.slice(2, 4);
99446
- if (["DD", "AU", "UD", "UA", "DU", "AA", "UU"].includes(xy)) {
99447
- conflictFiles.push(line.split(" ").at(-1) || "(unknown)");
99448
- }
99597
+ const addArgs = includeUntracked ? ["-A"] : ["-u"];
99598
+ await runGit(repo, ["add", ...addArgs], { cwd: repoRoot });
99599
+ const fullMsg = `adhdev: checkpoint ${message}`;
99600
+ let commitSha;
99601
+ try {
99602
+ await runGit(repo, ["commit", "-m", fullMsg], { cwd: repoRoot });
99603
+ const revResult = await runGit(repo, ["rev-parse", "HEAD"], { cwd: repoRoot });
99604
+ commitSha = revResult.stdout.trim();
99605
+ } catch (err) {
99606
+ const output = (err?.stdout || "") + (err?.stderr || "");
99607
+ if (/nothing to commit/i.test(output)) {
99608
+ return {
99609
+ workspace: repo.workspace,
99610
+ repoRoot,
99611
+ isGitRepo: true,
99612
+ message: fullMsg,
99613
+ status: "skipped",
99614
+ skipped: true,
99615
+ noop: true,
99616
+ reason: "nothing_to_commit",
99617
+ lastCheckedAt: Date.now()
99618
+ };
99449
99619
  }
99620
+ throw err;
99450
99621
  }
99451
99622
  return {
99452
- dirty: lines.length > 0,
99453
- changedFileCount: lines.length,
99454
- hasConflicts: conflictFiles.length > 0,
99455
- conflictFiles
99623
+ workspace: repo.workspace,
99624
+ repoRoot,
99625
+ isGitRepo: true,
99626
+ commit: commitSha,
99627
+ message: fullMsg,
99628
+ status: "created",
99629
+ lastCheckedAt: Date.now()
99456
99630
  };
99457
99631
  }
99458
- async function discoverRemotes(repoRoot, currentBranch) {
99459
- const names = (await git(repoRoot, ["remote"], true)).split(/\r?\n/).map((v) => v.trim()).filter(Boolean);
99460
- const remotes = [];
99461
- for (const name of names) {
99462
- const urls = (await git(repoRoot, ["remote", "get-url", "--all", name], true)).split(/\r?\n/).map((v) => v.trim()).filter(Boolean);
99463
- const identities2 = [...new Set(urls.map(normalizeRepoIdentity).filter(Boolean))];
99464
- remotes.push({ name, urls, identities: identities2 });
99632
+ async function gitStashPush(workspace, message, includeUntracked) {
99633
+ const repo = await resolveGitRepository(workspace);
99634
+ const repoRoot = repo.repoRoot;
99635
+ const stashArgs = ["stash", "push", "-m", message];
99636
+ if (includeUntracked) stashArgs.push("--include-untracked");
99637
+ const result = await runGit(repo, stashArgs, { cwd: repoRoot });
99638
+ if (/No local changes to save/i.test(result.stdout + result.stderr)) {
99639
+ throw new GitCommandError("git_command_failed", "Nothing to stash");
99465
99640
  }
99466
- const origin = remotes.find((remote) => remote.name === "origin");
99467
- const trackingRemoteName = await git(repoRoot, ["config", "--get", `branch.${currentBranch}.remote`], true);
99468
- const upstream = remotes.find((remote) => remote.name === trackingRemoteName) || remotes.find((remote) => remote.name === "upstream");
99469
- const identities = [...new Set(remotes.flatMap((remote) => remote.identities))];
99470
- const selectedRemote = origin || upstream || (remotes.length === 1 ? remotes[0] : void 0);
99471
99641
  return {
99472
- remotes,
99473
- origin,
99474
- upstream,
99475
- selectedRemote,
99476
- ambiguous: identities.length > 1 || !!remotes.find((remote) => remote.identities.length > 1)
99642
+ workspace: repo.workspace,
99643
+ repoRoot,
99644
+ isGitRepo: true,
99645
+ stashRef: "stash@{0}",
99646
+ message,
99647
+ lastCheckedAt: Date.now()
99477
99648
  };
99478
99649
  }
99479
- async function deriveLocalRepoIdentity(repoRoot) {
99480
- const raw = await git(repoRoot, ["rev-list", "--max-parents=0", "HEAD"], true);
99481
- const roots = raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => /^[0-9a-f]{40}$/i.test(line)).sort();
99482
- const root = roots[0];
99483
- return root ? `local/${root.toLowerCase()}` : "";
99484
- }
99485
- async function resolveDefaultBranch(repoRoot, remoteName, currentBranch) {
99486
- const symbolic = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", `refs/remotes/${remoteName}/HEAD`], true);
99487
- if (symbolic.startsWith(`${remoteName}/`)) return symbolic.slice(remoteName.length + 1);
99488
- for (const candidate of ["main", "master"]) {
99489
- const exists = await git(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/remotes/${remoteName}/${candidate}`], true) || await git(repoRoot, ["rev-parse", "--verify", "--quiet", `refs/heads/${candidate}`], true);
99490
- if (exists) return candidate;
99491
- }
99492
- return currentBranch;
99650
+ async function gitStashPop(workspace, stashRef) {
99651
+ const repo = await resolveGitRepository(workspace);
99652
+ const repoRoot = repo.repoRoot;
99653
+ const popArgs = stashRef ? ["stash", "pop", stashRef] : ["stash", "pop"];
99654
+ await runGit(repo, popArgs, { cwd: repoRoot });
99493
99655
  }
99494
- async function discoverGit(workspaceInput) {
99495
- const workspace = await canonicalPath(workspaceInput || process.cwd());
99496
- const repoRootRaw = await git(workspace, ["rev-parse", "--show-toplevel"], true);
99497
- if (!repoRootRaw) {
99498
- return failure2(
99499
- "not_git_repository",
99500
- `Not a Git repository: ${workspace}`,
99501
- "Choose a directory inside a Git checkout, or initialize/clone the repository before planning Repo Mesh onboarding."
99502
- );
99503
- }
99504
- const repoRoot = await canonicalPath(repoRootRaw);
99505
- const gitDirRaw = await git(repoRoot, ["rev-parse", "--git-dir"]);
99506
- const commonDirRaw = await git(repoRoot, ["rev-parse", "--git-common-dir"]);
99507
- const gitDir = await canonicalPath((0, import_node_path2.isAbsolute)(gitDirRaw) ? gitDirRaw : (0, import_node_path2.resolve)(repoRoot, gitDirRaw));
99508
- const commonDir = await canonicalPath((0, import_node_path2.isAbsolute)(commonDirRaw) ? commonDirRaw : (0, import_node_path2.resolve)(repoRoot, commonDirRaw));
99509
- const currentBranch = await git(repoRoot, ["symbolic-ref", "--quiet", "--short", "HEAD"], true);
99510
- const headCommit = await git(repoRoot, ["rev-parse", "HEAD"], true);
99511
- const worktrees = await listWorktrees(repoRoot);
99512
- const canonicalWorktrees = await Promise.all(worktrees.map(async (entry) => ({
99513
- ...entry,
99514
- path: await canonicalPath(entry.path)
99515
- })));
99516
- const mainWorktreeRoot = canonicalWorktrees[0]?.path || repoRoot;
99517
- const isMainCheckout = repoRoot === mainWorktreeRoot;
99518
- const isLinkedWorktree = gitDir !== commonDir;
99519
- const parentRepoRootRaw = await git((0, import_node_path2.dirname)(repoRoot), ["rev-parse", "--show-toplevel"], true);
99520
- const parentRepoRoot = parentRepoRootRaw ? await canonicalPath(parentRepoRootRaw) : "";
99521
- const isNestedWorktree = !!parentRepoRoot && parentRepoRoot !== repoRoot && pathInside(repoRoot, parentRepoRoot);
99522
- const status = parseStatus(await git(repoRoot, ["status", "--porcelain=v2", "--branch", "--untracked-files=normal"]));
99523
- const partial2 = {
99524
- inputPath: workspaceInput,
99525
- workspace,
99526
- repoRoot,
99527
- gitDir,
99528
- commonDir,
99529
- mainWorktreeRoot,
99530
- isMainCheckout,
99531
- isLinkedWorktree,
99532
- isNestedWorktree,
99533
- worktrees: canonicalWorktrees,
99534
- currentBranch,
99535
- headCommit,
99536
- ...status
99537
- };
99538
- if (!currentBranch) {
99539
- return failure2(
99540
- "detached_head",
99541
- `Detached HEAD at ${headCommit.slice(0, 12) || "(unknown commit)"}.`,
99542
- "Check out a named, safe branch before adding or cloning a Repo Mesh node.",
99543
- { discovery: partial2 }
99544
- );
99545
- }
99546
- const branchValid = await git(repoRoot, ["check-ref-format", "--branch", currentBranch], true);
99547
- if (!branchValid) {
99548
- return failure2(
99549
- "unsafe_branch",
99550
- `Current branch is not a safe Git branch name: ${currentBranch}`,
99551
- "Check out or create a valid named branch, then re-run the onboarding plan.",
99552
- { discovery: partial2 }
99553
- );
99554
- }
99555
- if (isNestedWorktree) {
99556
- return failure2(
99557
- "nested_worktree",
99558
- `Repository ${repoRoot} is nested inside Git checkout ${parentRepoRoot}.`,
99559
- "Choose the intended outer checkout or move the nested checkout outside it before Repo Mesh onboarding.",
99560
- { discovery: partial2 }
99561
- );
99562
- }
99563
- if (status.hasConflicts) {
99564
- return failure2(
99565
- "conflicted_workspace",
99566
- `Workspace has unresolved conflicts (${status.conflictFiles.join(", ")}).`,
99567
- "Resolve or abort the in-progress Git operation before adding this workspace to Repo Mesh.",
99568
- { discovery: partial2 }
99569
- );
99570
- }
99571
- const remoteDiscovery = await discoverRemotes(repoRoot, currentBranch);
99572
- if (!remoteDiscovery.remotes.length) {
99573
- const localIdentity = await deriveLocalRepoIdentity(repoRoot);
99574
- if (!localIdentity) {
99575
- return failure2(
99576
- "no_commits_for_local_identity",
99577
- "This repository has no remote and no commits, so a stable repository identity cannot be derived.",
99578
- "Create at least one commit (the root commit becomes the local identity), or pass an explicit repo identity when creating the mesh.",
99579
- { discovery: partial2 }
99580
- );
99656
+ async function gitCheckoutFiles(workspace, paths) {
99657
+ const repo = await resolveGitRepository(workspace);
99658
+ const repoRoot = repo.repoRoot;
99659
+ const normalizedPaths = [];
99660
+ for (const p of paths) {
99661
+ if (typeof p !== "string" || !p.trim() || p.includes("\0")) {
99662
+ throw new GitCommandError("invalid_args", `Invalid path: ${String(p)}`);
99581
99663
  }
99582
- const localDefaultBranch = await resolveDefaultBranch(repoRoot, "", currentBranch);
99583
- return {
99584
- ...partial2,
99585
- remotes: [],
99586
- origin: void 0,
99587
- upstream: void 0,
99588
- selectedRemote: "",
99589
- repoIdentity: localIdentity,
99590
- defaultBranch: localDefaultBranch
99591
- };
99592
- }
99593
- if (!remoteDiscovery.selectedRemote) {
99594
- const detail = remoteDiscovery.remotes.map((remote) => remote.name).join(", ");
99595
- return failure2(
99596
- "remote_not_selected",
99597
- `No canonical remote could be selected among multiple remotes (${detail}).`,
99598
- "Name one of them origin or upstream, set the current branch to track one of them, or pass an explicit repo identity when creating the mesh.",
99599
- { discovery: { ...partial2, remotes: remoteDiscovery.remotes, origin: remoteDiscovery.origin, upstream: remoteDiscovery.upstream } }
99600
- );
99601
- }
99602
- if (remoteDiscovery.ambiguous) {
99603
- const detail = remoteDiscovery.remotes.map((remote) => `${remote.name}=${remote.identities.join("|") || "(no URL)"}`).join(", ");
99604
- return failure2(
99605
- "ambiguous_remotes",
99606
- `Multiple unrelated remote identities were found: ${detail}`,
99607
- "Remove the ambiguity or explicitly choose/reconcile the canonical remote before onboarding.",
99608
- { discovery: { ...partial2, remotes: remoteDiscovery.remotes, origin: remoteDiscovery.origin, upstream: remoteDiscovery.upstream } }
99609
- );
99664
+ if (path15.isAbsolute(p)) {
99665
+ throw new GitCommandError("invalid_args", `Path must be repository-relative, not absolute: ${p}`);
99666
+ }
99667
+ const normalized = path15.normalize(p.trim()).split(path15.sep).join("/");
99668
+ if (normalized.startsWith("../") || normalized === "..") {
99669
+ throw new GitCommandError("path_outside_repo", `Path is outside repository root: ${p}`);
99670
+ }
99671
+ const absolutePath = path15.resolve(repoRoot, normalized);
99672
+ if (!isPathInside(repoRoot, absolutePath)) {
99673
+ throw new GitCommandError("path_outside_repo", `Path is outside repository root: ${p}`);
99674
+ }
99675
+ normalizedPaths.push(normalized);
99610
99676
  }
99611
- const repoIdentity = remoteDiscovery.selectedRemote.identities[0];
99612
- if (!repoIdentity) {
99613
- return failure2(
99614
- "remote_not_found",
99615
- `Remote '${remoteDiscovery.selectedRemote.name}' has no usable URL.`,
99616
- "Configure a valid fetch URL for the selected remote before onboarding.",
99617
- { discovery: partial2 }
99618
- );
99677
+ await runGit(repo, ["checkout", "--", ...normalizedPaths], { cwd: repoRoot });
99678
+ return { checkedOut: normalizedPaths };
99679
+ }
99680
+ async function gitGetRemoteUrl(workspace, remote) {
99681
+ const repo = await resolveGitRepository(workspace);
99682
+ const result = await runGit(repo, ["remote", "get-url", remote], { cwd: repo.repoRoot });
99683
+ const remoteUrl = result.stdout.trim();
99684
+ if (!remoteUrl) {
99685
+ throw new GitCommandError("git_command_failed", `Remote '${remote}' has no URL`);
99619
99686
  }
99620
- const defaultBranch = await resolveDefaultBranch(repoRoot, remoteDiscovery.selectedRemote.name, currentBranch);
99621
- return {
99622
- ...partial2,
99623
- remotes: remoteDiscovery.remotes,
99624
- origin: remoteDiscovery.origin,
99625
- upstream: remoteDiscovery.upstream,
99626
- selectedRemote: remoteDiscovery.selectedRemote.name,
99627
- repoIdentity,
99628
- defaultBranch
99629
- };
99687
+ return { remoteUrl, remote };
99630
99688
  }
99631
- async function membershipsFor(discovery, meshes) {
99632
- const matches = [];
99633
- for (const mesh of meshes) {
99634
- const compatible = normalizeRepoIdentity(mesh.repoIdentity) === discovery.repoIdentity;
99635
- let emittedMesh = false;
99636
- for (const node of mesh.nodes || []) {
99637
- const rawWorkspace = (0, import_node_path2.resolve)(node.workspace || "");
99638
- const canonicalWorkspace = await canonicalPath(rawWorkspace);
99639
- const canonicalRepoRoot = node.repoRoot ? await canonicalPath(node.repoRoot) : canonicalWorkspace;
99640
- const exactWorkspace = canonicalWorkspace === discovery.workspace;
99641
- if (exactWorkspace || canonicalRepoRoot === discovery.repoRoot) {
99642
- matches.push({
99643
- meshId: mesh.id,
99644
- meshName: mesh.name,
99645
- repoIdentity: normalizeRepoIdentity(mesh.repoIdentity),
99646
- compatible,
99647
- nodeId: node.id,
99648
- nodeWorkspace: node.workspace,
99649
- exactWorkspace
99650
- });
99651
- emittedMesh = true;
99652
- }
99689
+ async function gitPush(workspace, remote, branch, setUpstream) {
99690
+ const lastCheckedAt = Date.now();
99691
+ const repo = await resolveGitRepository(workspace);
99692
+ const repoRoot = repo.repoRoot;
99693
+ let resolvedBranch = branch;
99694
+ if (!resolvedBranch) {
99695
+ const branchResult = await runGit(repo, ["symbolic-ref", "--short", "HEAD"], { cwd: repoRoot });
99696
+ resolvedBranch = branchResult.stdout.trim();
99697
+ if (!resolvedBranch) {
99698
+ throw new GitCommandError("git_command_failed", "Cannot push: not on a branch (detached HEAD)");
99653
99699
  }
99654
- if (compatible && !emittedMesh) {
99655
- matches.push({
99656
- meshId: mesh.id,
99657
- meshName: mesh.name,
99658
- repoIdentity: normalizeRepoIdentity(mesh.repoIdentity),
99659
- compatible: true
99660
- });
99700
+ }
99701
+ const pushArgs = ["push"];
99702
+ if (setUpstream) pushArgs.push("--set-upstream");
99703
+ pushArgs.push(remote, resolvedBranch);
99704
+ let output = "";
99705
+ let newBranch = false;
99706
+ try {
99707
+ const result = await runGit(repo, pushArgs, { cwd: repoRoot });
99708
+ output = (result.stdout + result.stderr).trim();
99709
+ newBranch = /\[new branch\]/i.test(output);
99710
+ } catch (err) {
99711
+ const errOutput = (err?.stdout ?? "") + (err?.stderr ?? "");
99712
+ if (!setUpstream && /no upstream branch|set-upstream/i.test(errOutput)) {
99713
+ const retryArgs = ["push", "--set-upstream", remote, resolvedBranch];
99714
+ const retryResult = await runGit(repo, retryArgs, { cwd: repoRoot });
99715
+ output = (retryResult.stdout + retryResult.stderr).trim();
99716
+ newBranch = /\[new branch\]/i.test(output);
99717
+ } else {
99718
+ throw new GitCommandError("git_command_failed", errOutput || err?.message || "git push failed");
99661
99719
  }
99662
99720
  }
99663
- return matches;
99664
- }
99665
- function meshSummary(mesh) {
99666
99721
  return {
99667
- id: mesh.id,
99668
- name: mesh.name,
99669
- repoIdentity: normalizeRepoIdentity(mesh.repoIdentity),
99670
- defaultBranch: mesh.defaultBranch,
99671
- nodeCount: mesh.nodes?.length || 0
99722
+ workspace: repo.workspace,
99723
+ repoRoot,
99724
+ isGitRepo: true,
99725
+ remote,
99726
+ branch: resolvedBranch,
99727
+ output,
99728
+ newBranch,
99729
+ lastCheckedAt
99672
99730
  };
99673
99731
  }
99674
- function addStep(mesh, discovery) {
99675
- return {
99676
- command: "add_mesh_node",
99677
- description: "Explicitly register this existing checkout as a mesh node.",
99678
- writes: true,
99679
- approvalRequired: true,
99680
- args: {
99681
- meshId: mesh.id,
99682
- workspace: discovery.repoRoot,
99683
- repoRoot: discovery.repoRoot,
99684
- isLocalWorktree: discovery.isLinkedWorktree
99685
- }
99686
- };
99732
+ function formatOptionalGitLogRangeArg(flag, value) {
99733
+ return value ? [`${flag}=${value}`] : [];
99687
99734
  }
99688
- function initStep(discovery) {
99735
+ async function getGitLog(workspace, options) {
99736
+ const lastCheckedAt = Date.now();
99737
+ const repo = await resolveGitRepository(workspace);
99738
+ const repoRoot = repo.repoRoot;
99739
+ const boundedLimit = Math.max(1, Math.min(200, Math.floor(options.limit || 50)));
99740
+ const selectedPath = options.path ? validateGitLogPath(repoRoot, options.path) : void 0;
99741
+ const result = await runGit(
99742
+ repo,
99743
+ [
99744
+ "log",
99745
+ `--max-count=${boundedLimit}`,
99746
+ "--format=%H%x00%an%x00%ae%x00%at%x00%ct%x00%s",
99747
+ ...formatOptionalGitLogRangeArg("--since", options.since),
99748
+ ...formatOptionalGitLogRangeArg("--until", options.until),
99749
+ "--",
99750
+ ...selectedPath ? [selectedPath] : []
99751
+ ],
99752
+ { cwd: repoRoot }
99753
+ );
99754
+ const entries = result.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => {
99755
+ const [commit = "", authorName, authorEmail, authoredAt, committedAt, ...messageParts] = line.split("\0");
99756
+ return {
99757
+ commit,
99758
+ message: messageParts.join("\0"),
99759
+ authorName: authorName || void 0,
99760
+ authorEmail: authorEmail || void 0,
99761
+ authoredAt: authoredAt ? Number.parseInt(authoredAt, 10) * 1e3 : void 0,
99762
+ committedAt: committedAt ? Number.parseInt(committedAt, 10) * 1e3 : void 0
99763
+ };
99764
+ }).filter((entry) => entry.commit.length > 0);
99689
99765
  return {
99690
- command: "mesh_init",
99691
- description: "Preview suggested .adhdev configuration; persist only in a separate approved write call.",
99692
- writes: false,
99693
- approvalRequired: false,
99694
- args: { workspace: discovery.repoRoot, write: false, overwrite: false }
99766
+ workspace: repo.workspace,
99767
+ repoRoot,
99768
+ isGitRepo: true,
99769
+ entries,
99770
+ limit: boundedLimit,
99771
+ truncated: entries.length >= boundedLimit,
99772
+ lastCheckedAt
99695
99773
  };
99696
99774
  }
99697
- async function planMeshOnboarding(options) {
99698
- const discoveryResult = await discoverGit(options.workspace);
99699
- if (isPlanFailure(discoveryResult)) return discoveryResult;
99700
- const discovery = discoveryResult;
99701
- const operation = options.operation || "auto";
99702
- const meshes = options.meshes || listMeshesReadOnly();
99703
- const membership = await membershipsFor(discovery, meshes);
99704
- const requestedMesh = options.meshId ? meshes.find((mesh) => mesh.id === options.meshId) : void 0;
99705
- if (options.meshId && !requestedMesh) {
99706
- return failure2("mesh_not_found", `Mesh not found: ${options.meshId}`, "List meshes and choose an existing mesh id.", { discovery, membership });
99707
- }
99708
- if (requestedMesh && normalizeRepoIdentity(requestedMesh.repoIdentity) !== discovery.repoIdentity) {
99709
- return failure2(
99710
- "unrelated_repo_identity",
99711
- `Workspace identity '${discovery.repoIdentity}' does not match mesh '${requestedMesh.name}' identity '${normalizeRepoIdentity(requestedMesh.repoIdentity)}'.`,
99712
- "Choose a mesh for the same repository, or create a separate mesh for this repository.",
99713
- { discovery, membership }
99714
- );
99775
+ function validateGitLogPath(repoRoot, filePath) {
99776
+ if (!filePath.trim() || filePath.includes("\0")) {
99777
+ throw new GitCommandError("invalid_args", "path must be a non-empty repository-relative path");
99715
99778
  }
99716
- const compatibleMeshes = requestedMesh ? [requestedMesh] : meshes.filter((mesh) => normalizeRepoIdentity(mesh.repoIdentity) === discovery.repoIdentity);
99717
- if (compatibleMeshes.length > 1) {
99718
- return failure2(
99719
- "ambiguous_mesh",
99720
- `More than one compatible mesh exists for '${discovery.repoIdentity}': ${compatibleMeshes.map((mesh) => `${mesh.name} (${mesh.id})`).join(", ")}`,
99721
- "Pass an explicit mesh id to select the intended mesh.",
99722
- { discovery, membership }
99723
- );
99779
+ if (path15.isAbsolute(filePath)) {
99780
+ throw new GitCommandError("invalid_args", "path must be repository-relative");
99724
99781
  }
99725
- const compatibleMesh = compatibleMeshes[0];
99726
- const duplicate = membership.find((match) => match.compatible && match.nodeId && (!compatibleMesh || match.meshId === compatibleMesh.id));
99727
- if (operation !== "clone_worktree" && duplicate) {
99728
- return failure2(
99729
- duplicate.exactWorkspace ? "duplicate_workspace" : "duplicate_node",
99730
- `Workspace is already registered as node '${duplicate.nodeId}' in mesh '${duplicate.meshName}'.`,
99731
- "Use the existing node, or choose clone_worktree with a new branch for isolated work.",
99732
- { discovery, membership }
99733
- );
99782
+ const normalized = path15.normalize(filePath).split(path15.sep).join("/");
99783
+ const absolutePath = path15.resolve(repoRoot, normalized);
99784
+ if (!isPathInside(repoRoot, absolutePath) || normalized.startsWith("../") || normalized === "..") {
99785
+ throw new GitCommandError("path_outside_repo", "Git log path is outside the repository root");
99734
99786
  }
99735
- if (operation === "add_existing" && !compatibleMesh) {
99736
- return failure2(
99737
- "mesh_not_found",
99738
- `No compatible mesh exists for '${discovery.repoIdentity}'.`,
99739
- "Run the auto/create onboarding plan first, explicitly create the mesh, then add this workspace.",
99740
- { discovery, membership }
99741
- );
99787
+ return normalized;
99788
+ }
99789
+ var BUSY_STATUSES = /* @__PURE__ */ new Set(["streaming", "waiting_approval"]);
99790
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["idle", "error"]);
99791
+ var TurnSnapshotTracker = class {
99792
+ lastStatus = /* @__PURE__ */ new Map();
99793
+ onTurnCompleted;
99794
+ constructor(onTurnCompleted) {
99795
+ this.onTurnCompleted = onTurnCompleted;
99742
99796
  }
99743
- if (operation === "clone_worktree") {
99744
- const planWarnings = [];
99745
- if (!compatibleMesh) {
99746
- return failure2(
99747
- "mesh_not_found",
99748
- `No compatible mesh exists for '${discovery.repoIdentity}'.`,
99749
- "Create and add the base workspace first, then plan a cloned worktree node.",
99750
- { discovery, membership }
99751
- );
99752
- }
99753
- const dirtyBehavior = mergeAndNormalizePolicy(compatibleMesh.policy, void 0).dirtyWorkspaceBehavior;
99754
- if (discovery.dirty && dirtyBehavior === "block") {
99755
- return failure2(
99756
- "dirty_workspace",
99757
- `Workspace has ${discovery.changedFileCount} uncommitted change(s); a cloned worktree would not include them.`,
99758
- "Commit or stash the changes, then re-run the clone plan. (This mesh's dirtyWorkspaceBehavior is 'block'; set it to 'warn' to allow cloning from a dirty workspace.)",
99759
- { discovery, membership }
99760
- );
99761
- }
99762
- if (discovery.dirty) {
99763
- planWarnings.push(
99764
- `Source workspace has ${discovery.changedFileCount} uncommitted change(s). The new worktree is created from HEAD, so uncommitted changes are NOT included in it \u2014 commit them first if the cloned branch needs them.`
99765
- );
99766
- }
99767
- const branch = (options.branch || "").trim();
99768
- if (!branch || !await git(discovery.repoRoot, ["check-ref-format", "--branch", branch], true)) {
99769
- return failure2(
99770
- "unsafe_branch",
99771
- branch ? `Requested worktree branch is unsafe: ${branch}` : "A named branch is required to clone a new worktree node.",
99772
- "Provide a new valid branch name such as feat/my-task.",
99773
- { discovery, membership }
99774
- );
99797
+ record(sessionId, status, workspace) {
99798
+ const prev = this.lastStatus.get(sessionId);
99799
+ this.lastStatus.set(sessionId, status);
99800
+ if (workspace && prev && BUSY_STATUSES.has(prev) && TERMINAL_STATUSES.has(status)) {
99801
+ this.onTurnCompleted({ sessionId, workspace });
99775
99802
  }
99776
- const sourceNode = duplicate?.nodeId ? compatibleMesh.nodes.find((node) => node.id === duplicate.nodeId) : compatibleMesh.nodes.find((node) => !!node.workspace);
99777
- if (!sourceNode) {
99778
- return failure2(
99779
- "mesh_not_found",
99780
- `Mesh '${compatibleMesh.name}' has no source node to clone.`,
99781
- "Explicitly add this existing workspace as the first node, then clone a worktree.",
99782
- { discovery, membership }
99783
- );
99803
+ }
99804
+ forget(sessionId) {
99805
+ this.lastStatus.delete(sessionId);
99806
+ }
99807
+ };
99808
+ init_git_worktree();
99809
+ init_config();
99810
+ init_config_dir();
99811
+ init_embedded_path_health();
99812
+ var os11 = __toESM2(require("os"));
99813
+ var path17 = __toESM2(require("path"));
99814
+ var import_session_host_core22 = require_dist();
99815
+ init_config_dir();
99816
+ init_track_identity();
99817
+ var DEFAULT_SESSION_HOST_APP_NAME = IDENTITY.sessionHostName;
99818
+ var RESERVED_GLOBAL_SESSION_HOST_APP_NAME = IDENTITY.sessionHostName;
99819
+ var DEFAULT_STANDALONE_SESSION_HOST_APP_NAME = "adhdev-standalone";
99820
+ function getReservedStandaloneNamespaceWarning() {
99821
+ return `Standalone session-host namespace '${RESERVED_GLOBAL_SESSION_HOST_APP_NAME}' is reserved for the global daemon. Falling back to '${DEFAULT_STANDALONE_SESSION_HOST_APP_NAME}' for this standalone run.`;
99822
+ }
99823
+ function resolveSessionHostAppNameResolution2(options = {}) {
99824
+ const env2 = options.env || process.env;
99825
+ const explicit = typeof env2.ADHDEV_SESSION_HOST_NAME === "string" ? env2.ADHDEV_SESSION_HOST_NAME.trim() : "";
99826
+ if (explicit) {
99827
+ if (options.standalone && explicit === RESERVED_GLOBAL_SESSION_HOST_APP_NAME) {
99828
+ return {
99829
+ appName: DEFAULT_STANDALONE_SESSION_HOST_APP_NAME,
99830
+ warning: getReservedStandaloneNamespaceWarning(),
99831
+ source: "reserved-standalone-fallback"
99832
+ };
99784
99833
  }
99785
- const suggestedConfig2 = runMeshInit(compatibleMesh, discovery.repoRoot, options.detectedProviders || []);
99786
99834
  return {
99787
- success: true,
99788
- dryRun: true,
99789
- discovery,
99790
- membership,
99791
- compatibleMesh: meshSummary(compatibleMesh),
99792
- plan: {
99793
- kind: "clone_new_worktree",
99794
- summary: `Create an isolated worktree node from '${sourceNode.id}' on branch '${branch}'.`,
99795
- // Only a 'block' mesh actually requires a clean source; under the
99796
- // default 'warn' a dirty workspace is advisory, so reporting true here
99797
- // would misdescribe the plan the caller is approving.
99798
- requiresClean: dirtyBehavior === "block",
99799
- approvalRequired: true,
99800
- steps: [{
99801
- command: "clone_mesh_node",
99802
- description: "Explicitly create the branch/worktree and register the resulting node.",
99803
- writes: true,
99804
- approvalRequired: true,
99805
- args: {
99806
- meshId: compatibleMesh.id,
99807
- sourceNodeId: sourceNode.id,
99808
- branch,
99809
- baseBranch: compatibleMesh.defaultBranch || discovery.defaultBranch
99810
- }
99811
- }, initStep(discovery)],
99812
- alternatives: [{ kind: "add_existing_workspace", summary: "Register the supplied checkout without creating a worktree.", requiresClean: false }]
99813
- },
99814
- suggestedConfig: suggestedConfig2,
99815
- note: "Read-only plan only. No mesh, config, branch, remote or worktree state was changed.",
99816
- ...planWarnings.length ? { warnings: planWarnings } : {}
99835
+ appName: explicit,
99836
+ source: "explicit"
99817
99837
  };
99818
99838
  }
99819
- const suggestedConfig = runMeshInit(compatibleMesh || {}, discovery.repoRoot, options.detectedProviders || []);
99820
- if (compatibleMesh && operation !== "create_mesh") {
99821
- return {
99822
- success: true,
99823
- dryRun: true,
99824
- discovery,
99825
- membership,
99826
- compatibleMesh: meshSummary(compatibleMesh),
99827
- plan: {
99828
- kind: "add_existing_workspace",
99829
- summary: discovery.isLinkedWorktree ? `Register the existing linked worktree on '${discovery.currentBranch}' in '${compatibleMesh.name}'.` : `Register the main checkout as a base node in '${compatibleMesh.name}'.`,
99830
- requiresClean: false,
99831
- approvalRequired: true,
99832
- steps: [addStep(compatibleMesh, discovery), initStep(discovery)],
99833
- alternatives: [{
99834
- kind: "clone_new_worktree",
99835
- summary: `After a base node exists, create a clean isolated worktree from '${compatibleMesh.defaultBranch || discovery.defaultBranch}'.`,
99836
- requiresClean: true
99837
- }]
99838
- },
99839
- suggestedConfig,
99840
- note: "Read-only plan only. Adding the node and writing suggested configs require separate explicit approved calls."
99841
- };
99839
+ return {
99840
+ appName: options.standalone ? DEFAULT_STANDALONE_SESSION_HOST_APP_NAME : DEFAULT_SESSION_HOST_APP_NAME,
99841
+ source: "default"
99842
+ };
99843
+ }
99844
+ function resolveSessionHostAppName(options = {}) {
99845
+ return resolveSessionHostAppNameResolution2(options).appName;
99846
+ }
99847
+ var InstanceContextConflictError = class extends Error {
99848
+ constructor(message) {
99849
+ super(message);
99850
+ this.name = "InstanceContextConflictError";
99842
99851
  }
99843
- const identityRepoName = discovery.repoIdentity.split("/").filter(Boolean).at(-1);
99844
- const suggestedName = `${identityRepoName || (0, import_node_path2.basename)(discovery.mainWorktreeRoot || discovery.repoRoot)}-mesh`;
99845
- const createArgs = {
99846
- name: suggestedName,
99847
- repoIdentity: discovery.repoIdentity,
99848
- repoRemoteUrl: discovery.origin?.urls[0] || discovery.upstream?.urls[0] || discovery.remotes[0]?.urls[0],
99849
- defaultBranch: discovery.defaultBranch
99852
+ };
99853
+ var cached22 = null;
99854
+ function resolveInstanceContext(options = {}) {
99855
+ const env2 = options.env ?? process.env;
99856
+ const homeDir2 = options.homeDir ?? os11.homedir();
99857
+ const envDir = typeof env2.ADHDEV_CONFIG_DIR === "string" ? env2.ADHDEV_CONFIG_DIR.trim() : "";
99858
+ const explicitDir = typeof options.configDir === "string" ? options.configDir.trim() : "";
99859
+ if (explicitDir && envDir && (0, import_session_host_core22.canonicalizeInstancePath)(explicitDir) !== (0, import_session_host_core22.canonicalizeInstancePath)(envDir)) {
99860
+ throw new InstanceContextConflictError(
99861
+ `Conflicting instance identity: explicit configDir "${explicitDir}" vs ADHDEV_CONFIG_DIR "${envDir}". Refusing to merge mutable namespaces \u2014 fix the caller or unset one of the two.`
99862
+ );
99863
+ }
99864
+ const configDir = explicitDir || resolveConfigDir(env2, homeDir2);
99865
+ const trimmed = configDir.replace(/[\\/]+$/, "");
99866
+ return {
99867
+ configDir,
99868
+ canonicalConfigDir: (0, import_session_host_core22.canonicalizeInstancePath)(configDir),
99869
+ instanceDir: path17.basename(trimmed) || ".adhdev",
99870
+ isDefault: (0, import_session_host_core22.isDefaultInstanceConfigDir)(configDir, homeDir2),
99871
+ ipcKey: (0, import_session_host_core22.resolveSessionHostIpcKey)(configDir, homeDir2),
99872
+ sessionHostAppName: resolveSessionHostAppName({ standalone: options.standalone, env: env2 })
99873
+ };
99874
+ }
99875
+ function getProcessInstanceContext(options = {}) {
99876
+ const envDir = typeof process.env.ADHDEV_CONFIG_DIR === "string" ? process.env.ADHDEV_CONFIG_DIR.trim() : "";
99877
+ const key2 = `${envDir}|${os11.homedir()}|${options.standalone ? "standalone" : "daemon"}`;
99878
+ if (!cached22 || cached22.key !== key2) {
99879
+ cached22 = { key: key2, context: resolveInstanceContext({ standalone: options.standalone }) };
99880
+ }
99881
+ return cached22.context;
99882
+ }
99883
+ function resetProcessInstanceContextForTests() {
99884
+ cached22 = null;
99885
+ }
99886
+ init_workspaces();
99887
+ init_recent_activity();
99888
+ var path20 = __toESM2(require("path"));
99889
+ init_workspaces();
99890
+ init_summary_metadata();
99891
+ var MAX_SAVED_SESSIONS = 500;
99892
+ function normalizeWorkspace2(workspace) {
99893
+ if (!workspace) return "";
99894
+ try {
99895
+ return path20.resolve(expandPath(workspace));
99896
+ } catch {
99897
+ return path20.resolve(workspace);
99898
+ }
99899
+ }
99900
+ function buildSavedProviderSessionKey(providerSessionId) {
99901
+ return `saved:${providerSessionId.trim()}`;
99902
+ }
99903
+ function upsertSavedProviderSession(state, entry) {
99904
+ const providerSessionId = typeof entry.providerSessionId === "string" ? entry.providerSessionId.trim() : "";
99905
+ if (!providerSessionId) return state;
99906
+ const id = buildSavedProviderSessionKey(providerSessionId);
99907
+ const existing = (state.savedProviderSessions || []).find((item) => item.id === id);
99908
+ const nextEntry = {
99909
+ id,
99910
+ kind: entry.kind,
99911
+ providerType: entry.providerType,
99912
+ providerName: entry.providerName,
99913
+ providerSessionId,
99914
+ workspace: entry.workspace ? normalizeWorkspace2(entry.workspace) : void 0,
99915
+ summaryMetadata: normalizePersistedSummaryMetadata({
99916
+ summaryMetadata: entry.summaryMetadata
99917
+ }),
99918
+ title: entry.title,
99919
+ createdAt: existing?.createdAt || entry.createdAt || Date.now(),
99920
+ lastUsedAt: entry.lastUsedAt || Date.now()
99850
99921
  };
99922
+ const filtered = (state.savedProviderSessions || []).filter((item) => item.id !== id);
99851
99923
  return {
99852
- success: true,
99853
- dryRun: true,
99854
- discovery,
99855
- membership,
99856
- plan: {
99857
- kind: "create_mesh_and_onboard",
99858
- summary: `Create '${suggestedName}', add this ${discovery.isLinkedWorktree ? "linked worktree" : "checkout"} as its first node, then review suggested .adhdev configs.`,
99859
- requiresClean: false,
99860
- approvalRequired: true,
99861
- steps: [
99862
- {
99863
- command: "create_mesh",
99864
- description: "Explicitly create a machine-local mesh record.",
99865
- writes: true,
99866
- approvalRequired: true,
99867
- args: createArgs
99868
- },
99869
- {
99870
- command: "add_mesh_node",
99871
- description: "After creation, explicitly register this existing workspace as the first node.",
99872
- writes: true,
99873
- approvalRequired: true,
99874
- args: {
99875
- meshId: "<created mesh id>",
99876
- workspace: discovery.repoRoot,
99877
- repoRoot: discovery.repoRoot,
99878
- isLocalWorktree: discovery.isLinkedWorktree
99879
- }
99880
- },
99881
- initStep(discovery)
99882
- ],
99883
- alternatives: []
99884
- },
99885
- suggestedConfig,
99886
- note: "Read-only plan only. No mesh/config/worktree writes occurred; execute each write step separately after approval."
99924
+ ...state,
99925
+ savedProviderSessions: [nextEntry, ...filtered].slice(0, MAX_SAVED_SESSIONS)
99887
99926
  };
99888
99927
  }
99928
+ function getSavedProviderSessions(state, filters) {
99929
+ return [...state.savedProviderSessions || []].filter((entry) => {
99930
+ if (filters?.providerType && entry.providerType !== filters.providerType) return false;
99931
+ if (filters?.kind && entry.kind !== filters.kind) return false;
99932
+ return true;
99933
+ }).map((entry) => ({
99934
+ ...entry,
99935
+ summaryMetadata: normalizePersistedSummaryMetadata({
99936
+ summaryMetadata: entry.summaryMetadata
99937
+ })
99938
+ })).sort((a, b) => b.lastUsedAt - a.lastUsedAt);
99939
+ }
99940
+ init_mesh_config();
99941
+ init_dist();
99942
+ init_dist();
99943
+ init_dist();
99944
+ init_dist();
99945
+ init_coordinator_prompt();
99946
+ init_mesh_missions();
99947
+ init_mesh_task_stats();
99948
+ init_mesh_review_inbox();
99949
+ init_mesh_onboarding_plan();
99889
99950
  init_coordinator_registry();
99890
99951
  init_refine_config();
99891
99952
  init_worktree_bootstrap_config();
@@ -100291,12 +100352,13 @@ ${e?.stderr || ""}`;
100291
100352
  var import_node_child_process7 = require("child_process");
100292
100353
  init_mesh_runtime_store();
100293
100354
  init_mesh_config();
100355
+ init_mesh_onboarding_plan();
100294
100356
  var GIT_PROBE_TIMEOUT_MS = 3e3;
100295
100357
  var MAX_PROBE_COMMITS = 12;
100296
100358
  var SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
100297
- function isCommitReachable(workspace, sha) {
100359
+ function isCommitReachable(workspace, sha, defaultBranch) {
100298
100360
  try {
100299
- (0, import_node_child_process7.execFileSync)("git", ["merge-base", "--is-ancestor", sha, "origin/main"], {
100361
+ (0, import_node_child_process7.execFileSync)("git", ["merge-base", "--is-ancestor", sha, `origin/${defaultBranch}`], {
100300
100362
  cwd: workspace,
100301
100363
  timeout: GIT_PROBE_TIMEOUT_MS,
100302
100364
  stdio: ["ignore", "ignore", "ignore"]
@@ -100353,7 +100415,7 @@ ${e?.stderr || ""}`;
100353
100415
  }
100354
100416
  return commits;
100355
100417
  }
100356
- function collectGateConvergenceEvidence(meshId, gateId) {
100418
+ async function collectGateConvergenceEvidence(meshId, gateId) {
100357
100419
  try {
100358
100420
  const graphStore = MeshRuntimeStore.getInstance().graphStore();
100359
100421
  const gate = graphStore.getGate(gateId);
@@ -100362,19 +100424,21 @@ ${e?.stderr || ""}`;
100362
100424
  if (!workspace) return null;
100363
100425
  const upstream = collectUpstreamCommits(gate);
100364
100426
  if (upstream.length === 0) return null;
100427
+ const mesh = getMesh(meshId);
100428
+ const defaultBranch = await resolveRootDefaultBranch(workspace, mesh);
100365
100429
  const commits = upstream.map((ref) => ({
100366
100430
  ...ref,
100367
- reachedMain: isCommitReachable(workspace, ref.sha)
100431
+ reachedMain: isCommitReachable(workspace, ref.sha, defaultBranch)
100368
100432
  }));
100369
100433
  const allReachedMain = commits.every((c) => c.reachedMain === true);
100370
100434
  return {
100371
100435
  workspace,
100372
100436
  probedAt: (/* @__PURE__ */ new Date()).toISOString(),
100373
- probedAgainst: "local origin/main (no fetch)",
100437
+ probedAgainst: `local origin/${defaultBranch} (no fetch)`,
100374
100438
  commits,
100375
100439
  allReachedMain,
100376
100440
  ...allReachedMain ? {
100377
- hint: "Every upstream commit is already reachable from origin/main \u2014 the guarded work appears to have landed. Verify, then RELEASE this gate with these commits as evidence instead of re-running the action. (Evidence never auto-releases a gate; the decision is yours.)"
100441
+ hint: `Every upstream commit is already reachable from origin/${defaultBranch} \u2014 the guarded work appears to have landed. Verify, then RELEASE this gate with these commits as evidence instead of re-running the action. (Evidence never auto-releases a gate; the decision is yours.)`
100378
100442
  } : {}
100379
100443
  };
100380
100444
  } catch {
@@ -112671,7 +112735,26 @@ ${marker}`,
112671
112735
  // null = replication unavailable (node failed to open, or stats
112672
112736
  // threw); the key is always present so a caller can tell "no node"
112673
112737
  // from an older daemon that never reported the field at all.
112674
- seqscribe: ctx.deps.getSeqscribeStats?.() ?? null
112738
+ seqscribe: ctx.deps.getSeqscribeStats?.() ?? null,
112739
+ // Beacon staleness / sole-copy (design §7.1, mission b60d70b8).
112740
+ //
112741
+ // ★Unlike `seqscribe` above, this DOES carry topic names and peer
112742
+ // writer ids — that is the feature ("which topic is how far
112743
+ // ahead"), and it is why it appears on THIS local surface and on
112744
+ // the P2P payload, but never on `status_report` to the server. The
112745
+ // approved Beacon content exception (CLAUDE.md) covers the beacon
112746
+ // BOARD path; it does not widen the status path's allow-list.
112747
+ //
112748
+ // null = no beacon armed (standalone never arms one) or none yet on
112749
+ // this daemon, so it stays distinguishable from an older daemon
112750
+ // that never reported the field.
112751
+ beacon: ctx.deps.getBeaconDiagnostics?.() ?? null,
112752
+ // Phase 4 Stage 2 receive-side fleet view. This is a pure local
112753
+ // snapshot of fixed-key entries delivered by connection-scoped
112754
+ // seqscribe SUBs; it neither polls nor touches the server status
112755
+ // path. Raw numeric receive/comparison counters live alongside the
112756
+ // entries because no content or dynamic-key map is present.
112757
+ fleetStatusPeerView: ctx.deps.getFleetStatusPeerView?.() ?? null
112675
112758
  };
112676
112759
  },
112677
112760
  /**
@@ -132962,8 +133045,17 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
132962
133045
  const name = typeof args?.name === "string" ? args.name.trim() : "";
132963
133046
  const repoIdentity = typeof args?.repoIdentity === "string" ? args.repoIdentity.trim() : "";
132964
133047
  const repoRemoteUrl = typeof args?.repoRemoteUrl === "string" ? args.repoRemoteUrl.trim() : void 0;
132965
- const defaultBranch = typeof args?.defaultBranch === "string" ? args.defaultBranch.trim() : void 0;
133048
+ let defaultBranch = typeof args?.defaultBranch === "string" ? args.defaultBranch.trim() : void 0;
132966
133049
  if (!name) return { success: false, error: "name required" };
133050
+ if (!defaultBranch) {
133051
+ try {
133052
+ const { resolveRootDefaultBranch: resolveRootDefaultBranch2 } = await Promise.resolve().then(() => (init_mesh_onboarding_plan(), mesh_onboarding_plan_exports));
133053
+ const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : process.cwd();
133054
+ const detected = await resolveRootDefaultBranch2(workspace, void 0);
133055
+ if (detected) defaultBranch = detected;
133056
+ } catch {
133057
+ }
133058
+ }
132967
133059
  try {
132968
133060
  const { createMesh: createMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
132969
133061
  const meshHost = args?.meshHost && typeof args.meshHost === "object" && !Array.isArray(args.meshHost) ? args.meshHost : void 0;
@@ -134529,6 +134621,7 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
134529
134621
  };
134530
134622
  init_dist();
134531
134623
  init_mesh_fast_forward();
134624
+ init_mesh_init();
134532
134625
  init_cli_detector();
134533
134626
  init_mesh_refine_submodule_preflight();
134534
134627
  var fastForwardHandlers = {
@@ -134955,6 +135048,7 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
134955
135048
  }
134956
135049
  };
134957
135050
  init_cli_detector();
135051
+ init_mesh_onboarding_plan();
134958
135052
  var meshOnboardingHandlers = {
134959
135053
  plan_mesh_onboarding: async (ctx, args) => {
134960
135054
  const workspace = typeof args?.workspace === "string" && args.workspace.trim() ? args.workspace.trim() : process.cwd();
@@ -135108,7 +135202,7 @@ ${formatManifestValidationIssues2(validation2.issues)}`);
135108
135202
  }
135109
135203
  let convergenceEvidence = null;
135110
135204
  try {
135111
- convergenceEvidence = collectGateConvergenceEvidence(meshId, gateId);
135205
+ convergenceEvidence = await collectGateConvergenceEvidence(meshId, gateId);
135112
135206
  } catch {
135113
135207
  }
135114
135208
  return {
@@ -139833,6 +139927,49 @@ ${ptyResult.output.slice(-2e3)}`);
139833
139927
  ...seqscribeSummary ? { seqscribe: seqscribeSummary } : {}
139834
139928
  };
139835
139929
  }
139930
+ var FLEET_DAEMON_ID_RE = /^[A-Za-z0-9._:-]{1,160}$/;
139931
+ function fleetCount(value) {
139932
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
139933
+ }
139934
+ function projectFleetStatusEntry(raw) {
139935
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
139936
+ const value = raw;
139937
+ if (typeof value.daemonId !== "string" || !FLEET_DAEMON_ID_RE.test(value.daemonId)) return null;
139938
+ if (typeof value.at !== "string" || Number.isNaN(Date.parse(value.at))) return null;
139939
+ if (!["online", "reconnecting", "offline"].includes(String(value.onlineState))) return null;
139940
+ if (typeof value.p2pActive !== "boolean") return null;
139941
+ if (!value.sessionCounts || typeof value.sessionCounts !== "object" || Array.isArray(value.sessionCounts)) {
139942
+ return null;
139943
+ }
139944
+ const rawCounts = value.sessionCounts;
139945
+ const ideCount = fleetCount(rawCounts.ideCount);
139946
+ const cliCount = fleetCount(rawCounts.cliCount);
139947
+ const acpCount = fleetCount(rawCounts.acpCount);
139948
+ const idleCount = fleetCount(rawCounts.idleCount);
139949
+ const generatingCount = fleetCount(rawCounts.generatingCount);
139950
+ const waitingApprovalCount = fleetCount(rawCounts.waitingApprovalCount);
139951
+ const erroredCount = fleetCount(rawCounts.erroredCount);
139952
+ if (ideCount === null || cliCount === null || acpCount === null || idleCount === null || generatingCount === null || waitingApprovalCount === null || erroredCount === null) {
139953
+ return null;
139954
+ }
139955
+ const seqscribe = value.seqscribe && typeof value.seqscribe === "object" && !Array.isArray(value.seqscribe) ? buildCloudSeqscribeSummary(value.seqscribe) : void 0;
139956
+ return {
139957
+ daemonId: value.daemonId,
139958
+ at: new Date(value.at).toISOString(),
139959
+ onlineState: value.onlineState,
139960
+ p2pActive: value.p2pActive,
139961
+ sessionCounts: {
139962
+ ideCount,
139963
+ cliCount,
139964
+ acpCount,
139965
+ idleCount,
139966
+ generatingCount,
139967
+ waitingApprovalCount,
139968
+ erroredCount
139969
+ },
139970
+ ...seqscribe ? { seqscribe } : {}
139971
+ };
139972
+ }
139836
139973
  var SERVER_DEDUP_KEEPALIVE_REPORTS = 10;
139837
139974
  var DaemonStatusReporter = class {
139838
139975
  deps;
@@ -140062,6 +140199,18 @@ ${ptyResult.output.slice(-2e3)}`);
140062
140199
  LOG2.info("StatusReport", `\u2192${target} ${baseSummary}`);
140063
140200
  }
140064
140201
  }
140202
+ let beaconDiagnostics = null;
140203
+ try {
140204
+ beaconDiagnostics = this.deps.getBeaconDiagnostics?.() ?? null;
140205
+ } catch {
140206
+ beaconDiagnostics = null;
140207
+ }
140208
+ let fleetStatusPeerView = null;
140209
+ try {
140210
+ fleetStatusPeerView = this.deps.getFleetStatusPeerView?.() ?? null;
140211
+ } catch {
140212
+ fleetStatusPeerView = null;
140213
+ }
140065
140214
  const payload = {
140066
140215
  ...buildStatusSnapshot2({
140067
140216
  allStates,
@@ -140083,7 +140232,20 @@ ${ptyResult.output.slice(-2e3)}`);
140083
140232
  },
140084
140233
  profile: "live"
140085
140234
  }),
140086
- screenshotUsage: this.deps.getScreenshotUsage?.() || null
140235
+ screenshotUsage: this.deps.getScreenshotUsage?.() || null,
140236
+ // ★ Beacon staleness/sole-copy rides the P2P payload ONLY (mission
140237
+ // b60d70b8). It carries topic names and peer writer ids, which the
140238
+ // server status path forbids — and it cannot leak there, because the
140239
+ // server frame is built separately by
140240
+ // `buildCloudStatusReportPayload` from `allStates`, never from this
140241
+ // object. Omitted entirely when no beacon is armed, so "absent"
140242
+ // stays distinguishable from "armed but empty".
140243
+ ...beaconDiagnostics ? { beacon: beaconDiagnostics } : {},
140244
+ // Phase 4 Stage 2: latest fixed-key status received from each
140245
+ // seqscribe SUB peer. This is a cross-check beside the WS routing
140246
+ // view, not a replacement. The server frame below has no field for
140247
+ // it and continues to be built from its existing allow-list.
140248
+ ...fleetStatusPeerView ? { fleetStatusPeerView } : {}
140087
140249
  };
140088
140250
  const p2pSent = this.sendP2PPayload(payload);
140089
140251
  if (p2pSent) {
@@ -148003,6 +148165,155 @@ data: ${JSON.stringify(msg.data)}
148003
148165
  }
148004
148166
  LOG2.info("Seqscribe", `fleet secret stored (v${version2})`);
148005
148167
  }
148168
+ var BEACON_BOARD_TTL_MS = 3e4;
148169
+ function summarizeSoleCopy(args) {
148170
+ if (!args.haveBoard) return { verdict: "unknown", unknownReason: "no-board" };
148171
+ if (args.truncated > 0) return { verdict: "unknown", unknownReason: "truncated" };
148172
+ if (args.bestPeerSeq === null) {
148173
+ return { verdict: args.localSeq > 0 ? "sole-copy" : "replicated" };
148174
+ }
148175
+ return { verdict: args.bestPeerSeq < args.localSeq ? "sole-copy" : "replicated" };
148176
+ }
148177
+ function writerSeq(entry) {
148178
+ if (!entry || typeof entry !== "object") return null;
148179
+ const w = entry;
148180
+ const raw = w.retired === true ? w.finalSeq : w.contig;
148181
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : null;
148182
+ }
148183
+ function computeBeaconDiagnostics(args) {
148184
+ const now = args.now ?? Date.now();
148185
+ const board = args.board;
148186
+ const reports = board?.reports ?? [];
148187
+ const truncated = board?.truncated ?? 0;
148188
+ const haveBoard = board !== null;
148189
+ const peers = [];
148190
+ for (const report of reports) {
148191
+ if (report.node === args.node) continue;
148192
+ const topics = [];
148193
+ let worst = 0;
148194
+ for (const [topic, vec] of Object.entries(report.vectors ?? {})) {
148195
+ let topicBehind = 0;
148196
+ for (const [writer, entry] of Object.entries(vec?.writers ?? {})) {
148197
+ const theirs = writerSeq(entry);
148198
+ if (theirs === null) continue;
148199
+ const mine = writerSeq(args.localVectors[topic]?.writers?.[writer]) ?? 0;
148200
+ const lag = theirs - mine;
148201
+ if (lag > 0) topicBehind += lag;
148202
+ }
148203
+ if (topicBehind > 0) {
148204
+ topics.push({ node: report.node, topic, behind: topicBehind });
148205
+ if (topicBehind > worst) worst = topicBehind;
148206
+ }
148207
+ }
148208
+ const seenAt = Date.parse(report.at);
148209
+ const lastSeenAgeMs = Number.isNaN(seenAt) ? 0 : Math.max(0, now - seenAt);
148210
+ peers.push({
148211
+ node: report.node,
148212
+ behind: worst,
148213
+ topics: topics.sort((a, b) => b.behind - a.behind),
148214
+ lastSeen: report.at,
148215
+ lastSeenAgeMs
148216
+ });
148217
+ }
148218
+ peers.sort((a, b) => b.behind - a.behind || a.node.localeCompare(b.node));
148219
+ const soleCopy = [];
148220
+ for (const [topic, vec] of Object.entries(args.localVectors)) {
148221
+ for (const [writer, entry] of Object.entries(vec?.writers ?? {})) {
148222
+ const localSeq = writerSeq(entry);
148223
+ if (localSeq === null || localSeq <= 0) continue;
148224
+ let bestPeerSeq = null;
148225
+ for (const report of reports) {
148226
+ if (report.node === args.node) continue;
148227
+ const theirs = writerSeq(report.vectors?.[topic]?.writers?.[writer]);
148228
+ if (theirs === null) continue;
148229
+ if (bestPeerSeq === null || theirs > bestPeerSeq) bestPeerSeq = theirs;
148230
+ }
148231
+ const { verdict, unknownReason } = summarizeSoleCopy({
148232
+ localSeq,
148233
+ bestPeerSeq,
148234
+ truncated,
148235
+ haveBoard
148236
+ });
148237
+ if (verdict === "replicated") continue;
148238
+ soleCopy.push({
148239
+ topic,
148240
+ writer,
148241
+ localSeq,
148242
+ bestPeerSeq,
148243
+ unreplicated: verdict === "sole-copy" ? localSeq - (bestPeerSeq ?? 0) : 0,
148244
+ verdict,
148245
+ ...unknownReason ? { unknownReason } : {}
148246
+ });
148247
+ }
148248
+ }
148249
+ soleCopy.sort((a, b) => b.unreplicated - a.unreplicated || a.topic.localeCompare(b.topic));
148250
+ const boardAgeMs = board ? Math.max(0, now - board.capturedAt) : null;
148251
+ return {
148252
+ node: args.node,
148253
+ peers,
148254
+ maxBehind: peers.reduce((max, p) => p.behind > max ? p.behind : max, 0),
148255
+ soleCopy,
148256
+ truncated,
148257
+ soleCopyDeferred: truncated > 0,
148258
+ topicScope: board?.topicScope ?? [],
148259
+ boardAt: board ? new Date(board.capturedAt).toISOString() : null,
148260
+ boardAgeMs,
148261
+ stale: boardAgeMs === null || boardAgeMs > BEACON_BOARD_TTL_MS,
148262
+ // ★ Advisory only — see BeaconKeyStaleAdvisory.
148263
+ keyStaleAdvisory: args.keyStaleAdvisory ?? []
148264
+ };
148265
+ }
148266
+ function readKeyStaleAdvisory(handle, pairs2) {
148267
+ const out = [];
148268
+ for (const { topic, key: key2 } of pairs2) {
148269
+ try {
148270
+ const stale = handle.node.staleness(topic, key2);
148271
+ if (!stale?.keyStale) continue;
148272
+ out.push({
148273
+ topic,
148274
+ key: key2,
148275
+ latestKnown: stale.keyStale.latestKnown,
148276
+ haveLocally: stale.keyStale.haveLocally === true
148277
+ });
148278
+ } catch {
148279
+ }
148280
+ }
148281
+ return out;
148282
+ }
148283
+ function toBeaconDiagnosticsSummary(d) {
148284
+ return {
148285
+ node: d.node,
148286
+ peers: d.peers.map((p) => ({
148287
+ node: p.node,
148288
+ behind: p.behind,
148289
+ topics: p.topics.map((t) => ({ node: t.node, topic: t.topic, behind: t.behind })),
148290
+ lastSeen: p.lastSeen
148291
+ // `lastSeenAgeMs` intentionally dropped — see the header.
148292
+ })),
148293
+ maxBehind: d.maxBehind,
148294
+ soleCopy: d.soleCopy.map((s2) => ({
148295
+ topic: s2.topic,
148296
+ writer: s2.writer,
148297
+ localSeq: s2.localSeq,
148298
+ bestPeerSeq: s2.bestPeerSeq,
148299
+ unreplicated: s2.unreplicated,
148300
+ verdict: s2.verdict,
148301
+ ...s2.unknownReason ? { unknownReason: s2.unknownReason } : {}
148302
+ })),
148303
+ truncated: d.truncated,
148304
+ soleCopyDeferred: d.soleCopyDeferred,
148305
+ topicScope: [...d.topicScope],
148306
+ boardAt: d.boardAt,
148307
+ // `boardAgeMs` and `stale` intentionally dropped — both are derived
148308
+ // from Date.now() and would tick on every report.
148309
+ keyStaleAdvisory: d.keyStaleAdvisory.map((k) => ({
148310
+ topic: k.topic,
148311
+ key: k.key,
148312
+ latestKnown: k.latestKnown,
148313
+ haveLocally: k.haveLocally
148314
+ }))
148315
+ };
148316
+ }
148006
148317
  var import_fs24 = require("fs");
148007
148318
  var import_path26 = require("path");
148008
148319
  init_dist2();
@@ -148352,6 +148663,10 @@ data: ${JSON.stringify(msg.data)}
148352
148663
  dualWriteDroppedBucket: bucket(opts.dualWrite?.dropped ?? 0, BACKLOG_BUCKETS),
148353
148664
  dualWriteBackfilledBucket: bucket(opts.dualWrite?.backfilled ?? 0, BACKLOG_BUCKETS),
148354
148665
  parityMismatchBucket: bucket(opts.parity?.mismatches ?? 0, BACKLOG_BUCKETS),
148666
+ parityPersistentMismatchBucket: bucket(
148667
+ opts.parity?.persistentMismatches ?? 0,
148668
+ BACKLOG_BUCKETS
148669
+ ),
148355
148670
  parityRan: (opts.parity?.runs ?? 0) > 0,
148356
148671
  parityMissingInShadowBucket: bucket(opts.parity?.missingInShadow ?? 0, BACKLOG_BUCKETS),
148357
148672
  parityExtraInShadowBucket: bucket(opts.parity?.extraInShadow ?? 0, BACKLOG_BUCKETS),
@@ -148469,6 +148784,177 @@ data: ${JSON.stringify(msg.data)}
148469
148784
  }
148470
148785
  init_mesh_read_readiness();
148471
148786
  init_mesh_dual_write();
148787
+ init_dist();
148788
+ init_topics2();
148789
+ var FLEET_STATUS_SUB_VIEW = "tail";
148790
+ function emptyDiagnostics() {
148791
+ return {
148792
+ receivedEntries: 0,
148793
+ comparedEntries: 0,
148794
+ matchedEntries: 0,
148795
+ mismatchedEntries: 0,
148796
+ invalidEntries: 0,
148797
+ viewReplacements: 0
148798
+ };
148799
+ }
148800
+ function finiteInteger(value) {
148801
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
148802
+ }
148803
+ function compareOrder(a, b) {
148804
+ if (a.hlcL !== b.hlcL) return a.hlcL - b.hlcL;
148805
+ if (a.hlcC !== b.hlcC) return a.hlcC - b.hlcC;
148806
+ if (a.writer !== b.writer) return a.writer < b.writer ? -1 : 1;
148807
+ return a.seq - b.seq;
148808
+ }
148809
+ function cloneEntry(entry) {
148810
+ return {
148811
+ daemonId: entry.daemonId,
148812
+ at: entry.at,
148813
+ onlineState: entry.onlineState,
148814
+ p2pActive: entry.p2pActive,
148815
+ sessionCounts: { ...entry.sessionCounts },
148816
+ ...entry.seqscribe ? { seqscribe: { ...entry.seqscribe } } : {}
148817
+ };
148818
+ }
148819
+ function parseTailRow(row) {
148820
+ if (row.kind !== FLEET_STATUS_ENTRY_KIND || typeof row.payload !== "string") return null;
148821
+ const hlcL = finiteInteger(row.hlc_l);
148822
+ const hlcC = finiteInteger(row.hlc_c);
148823
+ const seq2 = finiteInteger(row.seq);
148824
+ if (hlcL === null || hlcC === null || seq2 === null || typeof row.writer !== "string") return null;
148825
+ let raw;
148826
+ try {
148827
+ raw = JSON.parse(row.payload);
148828
+ } catch {
148829
+ return null;
148830
+ }
148831
+ const entry = projectFleetStatusEntry(raw);
148832
+ if (!entry) return null;
148833
+ return { entry, order: { hlcL, hlcC, writer: row.writer, seq: seq2 } };
148834
+ }
148835
+ function createFleetStatusPeerViewConsumer(handle) {
148836
+ const active = /* @__PURE__ */ new Map();
148837
+ const latest = /* @__PURE__ */ new Map();
148838
+ const diagnostics = emptyDiagnostics();
148839
+ let nextGeneration2 = 1;
148840
+ let stopped = false;
148841
+ const closeSubscription = (peerId, removeView) => {
148842
+ const current = active.get(peerId);
148843
+ if (current) {
148844
+ active.delete(peerId);
148845
+ try {
148846
+ current.unsubscribeSnapshot();
148847
+ } catch {
148848
+ }
148849
+ try {
148850
+ current.unsubscribeDelta();
148851
+ } catch {
148852
+ }
148853
+ try {
148854
+ current.subscription.close();
148855
+ } catch {
148856
+ }
148857
+ }
148858
+ if (removeView) latest.delete(peerId);
148859
+ };
148860
+ const acceptRows = (peerId, generation, rows, replaceSnapshot) => {
148861
+ if (stopped || active.get(peerId)?.generation !== generation) return;
148862
+ let newest = null;
148863
+ for (const row of rows) {
148864
+ diagnostics.receivedEntries++;
148865
+ const parsed = parseTailRow(row);
148866
+ if (!parsed) {
148867
+ diagnostics.invalidEntries++;
148868
+ continue;
148869
+ }
148870
+ diagnostics.comparedEntries++;
148871
+ if (!daemonIdsEquivalent(parsed.entry.daemonId, peerId)) {
148872
+ diagnostics.mismatchedEntries++;
148873
+ continue;
148874
+ }
148875
+ diagnostics.matchedEntries++;
148876
+ if (!newest || compareOrder(parsed.order, newest.order) > 0) newest = parsed;
148877
+ }
148878
+ if (replaceSnapshot) {
148879
+ if (!newest) {
148880
+ latest.delete(peerId);
148881
+ return;
148882
+ }
148883
+ latest.set(peerId, newest);
148884
+ diagnostics.viewReplacements++;
148885
+ return;
148886
+ }
148887
+ if (!newest) return;
148888
+ const current = latest.get(peerId);
148889
+ if (!current || compareOrder(newest.order, current.order) > 0) {
148890
+ latest.set(peerId, newest);
148891
+ diagnostics.viewReplacements++;
148892
+ }
148893
+ };
148894
+ return {
148895
+ attachPeer(peerId, peer) {
148896
+ if (stopped || !peerId || peer.state() !== "ready") return false;
148897
+ closeSubscription(peerId, true);
148898
+ const generation = nextGeneration2++;
148899
+ let subscription = null;
148900
+ let unsubscribeSnapshot = null;
148901
+ let unsubscribeDelta = null;
148902
+ try {
148903
+ subscription = handle.node.subscribe(peer, {
148904
+ view: FLEET_STATUS_SUB_VIEW,
148905
+ params: { topic: FLEET_STATUS_TOPIC }
148906
+ });
148907
+ unsubscribeSnapshot = subscription.onSnapshot((rows) => {
148908
+ acceptRows(peerId, generation, rows, true);
148909
+ });
148910
+ unsubscribeDelta = subscription.onDelta((changes) => {
148911
+ acceptRows(peerId, generation, changes.upserts, false);
148912
+ });
148913
+ active.set(peerId, {
148914
+ generation,
148915
+ subscription,
148916
+ unsubscribeSnapshot,
148917
+ unsubscribeDelta
148918
+ });
148919
+ return true;
148920
+ } catch {
148921
+ try {
148922
+ unsubscribeSnapshot?.();
148923
+ } catch {
148924
+ }
148925
+ try {
148926
+ unsubscribeDelta?.();
148927
+ } catch {
148928
+ }
148929
+ try {
148930
+ subscription?.close();
148931
+ } catch {
148932
+ }
148933
+ latest.delete(peerId);
148934
+ return false;
148935
+ }
148936
+ },
148937
+ detachPeer(peerId) {
148938
+ closeSubscription(peerId, true);
148939
+ },
148940
+ snapshot() {
148941
+ const peers = Array.from(latest.values()).map(({ entry }) => cloneEntry(entry)).sort((a, b) => a.daemonId.localeCompare(b.daemonId));
148942
+ return {
148943
+ peers,
148944
+ diagnostics: {
148945
+ subscribedPeers: active.size,
148946
+ ...diagnostics
148947
+ }
148948
+ };
148949
+ },
148950
+ stop() {
148951
+ if (stopped) return;
148952
+ stopped = true;
148953
+ for (const peerId of Array.from(active.keys())) closeSubscription(peerId, true);
148954
+ latest.clear();
148955
+ }
148956
+ };
148957
+ }
148472
148958
  init_mesh_parity();
148473
148959
  init_mesh_config();
148474
148960
  init_logger();
@@ -148839,6 +149325,7 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
148839
149325
  commandHandler.setAgentStreamManager(agentStreamManager);
148840
149326
  let seqscribeNodeRef;
148841
149327
  let seqscribeCollector;
149328
+ let componentsRef;
148842
149329
  const router = new DaemonCommandRouter({
148843
149330
  commandHandler,
148844
149331
  cliManager,
@@ -148892,6 +149379,7 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
148892
149379
  parity: {
148893
149380
  runs: parity.runs,
148894
149381
  mismatches: parity.mismatches,
149382
+ persistentMismatches: parity.persistentMismatches,
148895
149383
  missingInShadow: parity.missingInShadow,
148896
149384
  extraInShadow: parity.extraInShadow,
148897
149385
  fieldMismatch: parity.fieldMismatch
@@ -148904,7 +149392,39 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
148904
149392
  );
148905
149393
  return null;
148906
149394
  }
148907
- }
149395
+ },
149396
+ // Beacon staleness / sole-copy for `get_status_metadata` (§7.1, mission
149397
+ // b60d70b8). A getter for the same reason as getSeqscribeStats — but
149398
+ // one level more so: the beacon is armed by the HOST on its first
149399
+ // authenticated epoch, long after this router is constructed, so the
149400
+ // closure reads `components.seqscribeBeacon` at call time.
149401
+ //
149402
+ // ★Unlike getSeqscribeStats above, the value here DOES carry topic
149403
+ // names and peer writer ids. That is the feature, and it is why it is
149404
+ // exposed on this LOCAL surface (and the P2P payload) only — never on
149405
+ // status_report. `buildCloudSeqscribeSummary` is a fixed-key allow-list
149406
+ // that could not forward it even by accident.
149407
+ //
149408
+ // `diagnostics()` performs no I/O: it reads the last board the beacon
149409
+ // captured, so an on-demand `get_status_metadata` cannot turn into a
149410
+ // Beacon traffic source (§7.1.2's idle-silence property).
149411
+ getBeaconDiagnostics: () => {
149412
+ const beacon = componentsRef?.seqscribeBeacon;
149413
+ if (!beacon) return null;
149414
+ try {
149415
+ return toBeaconDiagnosticsSummary(beacon.diagnostics());
149416
+ } catch (error48) {
149417
+ LOG2.info(
149418
+ "Seqscribe",
149419
+ `beacon diagnostics unavailable for get_status_metadata: ${error48 instanceof Error ? error48.message : String(error48)}`
149420
+ );
149421
+ return null;
149422
+ }
149423
+ },
149424
+ // Phase 4 Stage 2: pure local snapshot of statuses received through
149425
+ // per-peer SUB tail subscriptions. The cloud host supplies PeerHandles
149426
+ // after this core assembly returns; the slot is read at call time.
149427
+ getFleetStatusPeerView: () => componentsRef?.seqscribeFleetStatusPeerView?.snapshot() ?? null
148908
149428
  });
148909
149429
  poller = new AgentStreamPoller({
148910
149430
  agentStreamManager,
@@ -148934,8 +149454,19 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
148934
149454
  onMeshCoordinatorEventForwarded: config2.onMeshCoordinatorEventForwarded,
148935
149455
  statusInstanceId: config2.statusInstanceId
148936
149456
  };
149457
+ componentsRef = components;
148937
149458
  components.seqscribeNode = tryOpenDaemonSeqscribeNode({ daemonId: config2.statusInstanceId });
148938
149459
  seqscribeNodeRef = components.seqscribeNode;
149460
+ if (components.seqscribeNode) {
149461
+ try {
149462
+ components.seqscribeFleetStatusPeerView = createFleetStatusPeerViewConsumer(components.seqscribeNode);
149463
+ } catch (error48) {
149464
+ LOG2.warn(
149465
+ "Seqscribe",
149466
+ `fleet.status SUB consumer unavailable: ${error48 instanceof Error ? error48.message : String(error48)}`
149467
+ );
149468
+ }
149469
+ }
148939
149470
  if (components.seqscribeNode) {
148940
149471
  try {
148941
149472
  const node = components.seqscribeNode;
@@ -149033,7 +149564,8 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149033
149564
  seqscribeNode,
149034
149565
  seqscribeProbe,
149035
149566
  seqscribeParityLoop,
149036
- seqscribeCollector
149567
+ seqscribeCollector,
149568
+ seqscribeFleetStatusPeerView
149037
149569
  } = components;
149038
149570
  poller.stop();
149039
149571
  cdpInitializer.stop();
@@ -149085,6 +149617,10 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149085
149617
  configureFleetStatusShadow(null);
149086
149618
  } catch {
149087
149619
  }
149620
+ try {
149621
+ seqscribeFleetStatusPeerView?.stop();
149622
+ } catch {
149623
+ }
149088
149624
  try {
149089
149625
  configureMeshReadModel(null);
149090
149626
  } catch {
@@ -149588,6 +150124,7 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149588
150124
  var TOPIC_RE2 = /^[A-Za-z0-9._*-]{1,160}$/;
149589
150125
  var MAX_TOPICS_PER_REPORT = 512;
149590
150126
  var MAX_WRITERS_PER_TOPIC = 512;
150127
+ var MAX_HINTS_PER_TOPIC = 512;
149591
150128
  function finiteCount(v) {
149592
150129
  return typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.floor(v) : void 0;
149593
150130
  }
@@ -149609,6 +150146,31 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149609
150146
  if (contig === void 0 || chain === void 0) return null;
149610
150147
  return rgen === void 0 ? { contig, chain } : { contig, chain, rgen };
149611
150148
  }
150149
+ function projectHints(raw) {
150150
+ if (!raw || typeof raw !== "object") return void 0;
150151
+ const hints = {};
150152
+ let topicCount = 0;
150153
+ for (const [topic, rawTopicHints] of Object.entries(raw)) {
150154
+ if (topicCount >= MAX_TOPICS_PER_REPORT) break;
150155
+ if (!TOPIC_RE2.test(topic) || !rawTopicHints || typeof rawTopicHints !== "object") continue;
150156
+ const topicHints = {};
150157
+ let hintCount = 0;
150158
+ for (const [keyHash, rawPosition] of Object.entries(rawTopicHints)) {
150159
+ if (hintCount >= MAX_HINTS_PER_TOPIC) break;
150160
+ if (!CHAIN_RE2.test(keyHash)) continue;
150161
+ if (!Array.isArray(rawPosition) || rawPosition.length !== 2) continue;
150162
+ const writer = typeof rawPosition[0] === "string" && WRITER_ID_RE.test(rawPosition[0]) ? rawPosition[0] : void 0;
150163
+ const seq2 = finiteCount(rawPosition[1]);
150164
+ if (writer === void 0 || seq2 === void 0) continue;
150165
+ topicHints[keyHash] = [writer, seq2];
150166
+ hintCount++;
150167
+ }
150168
+ if (hintCount === 0) continue;
150169
+ hints[topic] = topicHints;
150170
+ topicCount++;
150171
+ }
150172
+ return topicCount > 0 ? hints : void 0;
150173
+ }
149612
150174
  function projectBeaconReport(raw) {
149613
150175
  if (!raw || typeof raw !== "object") return null;
149614
150176
  const v = raw;
@@ -149641,7 +150203,8 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149641
150203
  topicCount++;
149642
150204
  }
149643
150205
  }
149644
- return { node, at, vectors };
150206
+ const hints = projectHints(v.hints);
150207
+ return hints ? { node, at, vectors, hints } : { node, at, vectors };
149645
150208
  }
149646
150209
  function assertNoPlaintextHintTopics(defs) {
149647
150210
  const offenders = defs.filter((d) => d.policy.hintKeys === "plain").map((d) => d.topic);
@@ -149701,9 +150264,16 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149701
150264
  }
149702
150265
  assertNoPlaintextHintTopics(handle.topics);
149703
150266
  const counters5 = emptyCounters();
149704
- const scopeOf = opts.topicScope ?? defaultBeaconTopicScope;
150267
+ const scopeOf = opts.topicScope ?? ((vectors) => [
150268
+ .../* @__PURE__ */ new Set([
150269
+ ...defaultBeaconTopicScope(vectors),
150270
+ ...handle.topics.filter((definition) => definition.policy.hintKeys === "hash").map((definition) => definition.topic)
150271
+ ])
150272
+ ]);
149705
150273
  let stopped = false;
149706
150274
  let lastScope = [];
150275
+ let lastLocalHints;
150276
+ let lastBoard = null;
149707
150277
  const doPut = async (rawReport) => {
149708
150278
  const projected = projectBeaconReport(rawReport);
149709
150279
  if (!projected) {
@@ -149711,6 +150281,7 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149711
150281
  LOG2.warn("Seqscribe", "beacon report failed the content projection; not sent");
149712
150282
  return;
149713
150283
  }
150284
+ lastLocalHints = projected.hints;
149714
150285
  lastScope = scopeOf(projected.vectors);
149715
150286
  try {
149716
150287
  await transport.put(projected);
@@ -149789,6 +150360,12 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149789
150360
  );
149790
150361
  }
149791
150362
  const clean2 = reports.map((r) => projectBeaconReport(r)).filter((r) => r !== null);
150363
+ lastBoard = {
150364
+ reports: clean2,
150365
+ truncated,
150366
+ topicScope: [...lastScope],
150367
+ capturedAt: Date.now()
150368
+ };
149792
150369
  LOG2.info(
149793
150370
  "Seqscribe",
149794
150371
  `beacon get writer=${handle.writerId} peers=${clean2.length} topics=${lastScope.length}`
@@ -149820,18 +150397,75 @@ ${upgradeFailureNotice.notice}${supersededHint}`);
149820
150397
  );
149821
150398
  },
149822
150399
  counters: () => ({ ...counters5 }),
150400
+ seedKnownBoard(seed) {
150401
+ if (stopped || !Array.isArray(seed?.reports)) return;
150402
+ const clean2 = seed.reports.map((report) => projectBeaconReport(report)).filter((report) => report !== null);
150403
+ const truncated = typeof seed.truncated === "number" && Number.isFinite(seed.truncated) && seed.truncated >= 0 ? Math.floor(seed.truncated) : 0;
150404
+ const topicScope = [...new Set(clean2.flatMap((report) => [
150405
+ ...Object.keys(report.vectors),
150406
+ ...Object.keys(report.hints ?? {})
150407
+ ]))];
150408
+ try {
150409
+ handle.node.setKnownVectors(clean2);
150410
+ lastBoard = {
150411
+ reports: clean2,
150412
+ truncated,
150413
+ topicScope,
150414
+ capturedAt: Date.now()
150415
+ };
150416
+ if (truncated > 0) counters5.truncated += truncated;
150417
+ LOG2.info(
150418
+ "Seqscribe",
150419
+ `beacon auth seed writer=${handle.writerId} peers=${clean2.length} topics=${topicScope.length} truncated=${truncated}`
150420
+ );
150421
+ } catch (err) {
150422
+ LOG2.info(
150423
+ "Seqscribe",
150424
+ `beacon auth seed rejected (advisory, ignored): ${err instanceof Error ? err.message : String(err)}`
150425
+ );
150426
+ }
150427
+ },
149823
150428
  async pushNow() {
149824
150429
  if (stopped) return;
149825
150430
  try {
149826
150431
  await doPut({
149827
150432
  node: handle.writerId,
149828
150433
  at: (/* @__PURE__ */ new Date()).toISOString(),
149829
- vectors: handle.node.vectors()
150434
+ vectors: handle.node.vectors(),
150435
+ ...lastLocalHints ? { hints: lastLocalHints } : {}
149830
150436
  });
149831
150437
  const board = await doGet();
149832
150438
  if (!stopped) handle.node.setKnownVectors(board);
149833
150439
  } catch {
149834
150440
  }
150441
+ },
150442
+ diagnostics() {
150443
+ let localVectors = {};
150444
+ try {
150445
+ localVectors = handle.node.vectors();
150446
+ } catch {
150447
+ }
150448
+ return computeBeaconDiagnostics({
150449
+ node: handle.writerId,
150450
+ localVectors,
150451
+ board: lastBoard,
150452
+ // ★ Advisory only (§5.7a). Query the hash keys peers actually
150453
+ // advertised; the upstream reader chooses the raw max seq
150454
+ // across writers, so this must never become a correctness gate.
150455
+ keyStaleAdvisory: readKeyStaleAdvisory(
150456
+ handle,
150457
+ [...new Map(
150458
+ (lastBoard?.reports ?? []).filter((report) => report.node !== handle.writerId).flatMap(
150459
+ (report) => Object.entries(report.hints ?? {}).flatMap(
150460
+ ([topic, topicHints]) => Object.keys(topicHints).map((key2) => [
150461
+ `${topic}\0${key2}`,
150462
+ { topic, key: key2 }
150463
+ ])
150464
+ )
150465
+ )
150466
+ ).values()]
150467
+ )
150468
+ });
149835
150469
  }
149836
150470
  };
149837
150471
  handle.onClose(() => beaconHandle.stop());