@adhdev/daemon-core 0.9.82-rc.172 → 0.9.82-rc.173

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
@@ -255,6 +255,263 @@ var init_git_executor = __esm({
255
255
  }
256
256
  });
257
257
 
258
+ // src/git/git-status.ts
259
+ async function getGitRepoStatus(workspace, options = {}) {
260
+ const lastCheckedAt = Date.now();
261
+ const includeSubmodules = options.includeSubmodules !== false;
262
+ try {
263
+ const repo = await resolveGitRepository(workspace, options);
264
+ let parsed = await readPorcelainStatus(repo, options);
265
+ let upstreamProbe = getInitialUpstreamProbe(parsed);
266
+ if (options.refreshUpstream) {
267
+ upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
268
+ if (upstreamProbe.upstreamStatus === "fresh") {
269
+ parsed = await readPorcelainStatus(repo, options);
270
+ }
271
+ }
272
+ const head = await readHead(repo, options);
273
+ const stashCount = await readStashCount(repo, options);
274
+ let submodules;
275
+ if (includeSubmodules) {
276
+ submodules = await getSubmoduleStatuses(repo, options);
277
+ }
278
+ return {
279
+ workspace: repo.workspace,
280
+ repoRoot: repo.repoRoot,
281
+ isGitRepo: true,
282
+ branch: parsed.branch,
283
+ headCommit: head.commit,
284
+ headMessage: head.message,
285
+ upstream: parsed.upstream,
286
+ upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
287
+ upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
288
+ upstreamFetchError: upstreamProbe.upstreamFetchError,
289
+ ahead: parsed.ahead,
290
+ behind: parsed.behind,
291
+ staged: parsed.staged,
292
+ modified: parsed.modified,
293
+ untracked: parsed.untracked,
294
+ deleted: parsed.deleted,
295
+ renamed: parsed.renamed,
296
+ hasConflicts: parsed.conflictFiles.length > 0,
297
+ conflictFiles: parsed.conflictFiles,
298
+ stashCount,
299
+ lastCheckedAt,
300
+ submodules
301
+ };
302
+ } catch (error) {
303
+ if (error instanceof GitCommandError) {
304
+ return emptyStatus(workspace, lastCheckedAt, error);
305
+ }
306
+ return emptyStatus(
307
+ workspace,
308
+ lastCheckedAt,
309
+ new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
310
+ );
311
+ }
312
+ }
313
+ async function readPorcelainStatus(repo, options) {
314
+ const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
315
+ return parsePorcelainV2Status(statusOutput.stdout);
316
+ }
317
+ function getInitialUpstreamProbe(parsed) {
318
+ return {
319
+ upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
320
+ };
321
+ }
322
+ async function refreshTrackedUpstream(repo, parsed, options) {
323
+ if (!parsed.upstream || !parsed.branch) {
324
+ return { upstreamStatus: "no_upstream" };
325
+ }
326
+ const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
327
+ if (!remoteName) {
328
+ return {
329
+ upstreamStatus: "stale",
330
+ upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
331
+ };
332
+ }
333
+ try {
334
+ await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
335
+ return {
336
+ upstreamStatus: "fresh",
337
+ upstreamFetchedAt: Date.now()
338
+ };
339
+ } catch (error) {
340
+ return {
341
+ upstreamStatus: "stale",
342
+ upstreamFetchError: formatGitError(error)
343
+ };
344
+ }
345
+ }
346
+ async function readBranchRemote(repo, branch, options) {
347
+ try {
348
+ const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
349
+ return result.stdout.trim() || null;
350
+ } catch {
351
+ return null;
352
+ }
353
+ }
354
+ function inferRemoteName(upstream) {
355
+ const [remoteName] = upstream.split("/");
356
+ return remoteName?.trim() || null;
357
+ }
358
+ function formatGitError(error) {
359
+ if (error instanceof GitCommandError) {
360
+ return error.stderr || error.message;
361
+ }
362
+ if (error instanceof Error) {
363
+ return error.message;
364
+ }
365
+ return String(error);
366
+ }
367
+ function parsePorcelainV2Status(output) {
368
+ const parsed = {
369
+ branch: null,
370
+ upstream: null,
371
+ ahead: 0,
372
+ behind: 0,
373
+ staged: 0,
374
+ modified: 0,
375
+ untracked: 0,
376
+ deleted: 0,
377
+ renamed: 0,
378
+ conflictFiles: []
379
+ };
380
+ for (const line of output.split("\n")) {
381
+ if (!line) continue;
382
+ if (line.startsWith("# branch.head ")) {
383
+ const branch = line.slice("# branch.head ".length).trim();
384
+ parsed.branch = branch && branch !== "(detached)" ? branch : null;
385
+ continue;
386
+ }
387
+ if (line.startsWith("# branch.upstream ")) {
388
+ parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
389
+ continue;
390
+ }
391
+ if (line.startsWith("# branch.ab ")) {
392
+ const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
393
+ if (match) {
394
+ parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
395
+ parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
396
+ }
397
+ continue;
398
+ }
399
+ if (line.startsWith("? ")) {
400
+ parsed.untracked += 1;
401
+ continue;
402
+ }
403
+ if (line.startsWith("u ")) {
404
+ const fields = line.split(" ");
405
+ const filePath = fields.slice(10).join(" ");
406
+ if (filePath) parsed.conflictFiles.push(filePath);
407
+ continue;
408
+ }
409
+ if (line.startsWith("1 ") || line.startsWith("2 ")) {
410
+ const fields = line.split(" ");
411
+ const xy = fields[1] ?? "..";
412
+ const indexStatus = xy[0] ?? ".";
413
+ const worktreeStatus = xy[1] ?? ".";
414
+ if (isStagedStatus(indexStatus)) parsed.staged += 1;
415
+ if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
416
+ if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
417
+ if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
418
+ if (xy.includes("U")) {
419
+ const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
420
+ if (filePath) parsed.conflictFiles.push(filePath);
421
+ }
422
+ }
423
+ }
424
+ parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
425
+ return parsed;
426
+ }
427
+ async function readHead(repo, options) {
428
+ try {
429
+ const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
430
+ const text = result.stdout.trimEnd();
431
+ if (!text) return { commit: null, message: null };
432
+ const [commit, ...messageParts] = text.split("\0");
433
+ return {
434
+ commit: commit || null,
435
+ message: messageParts.join("\0") || null
436
+ };
437
+ } catch {
438
+ return { commit: null, message: null };
439
+ }
440
+ }
441
+ async function readStashCount(repo, options) {
442
+ try {
443
+ const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
444
+ return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
445
+ } catch {
446
+ return 0;
447
+ }
448
+ }
449
+ function isStagedStatus(status) {
450
+ return status !== "." && status !== "?" && status !== "U";
451
+ }
452
+ function emptyStatus(workspace, lastCheckedAt, error) {
453
+ return {
454
+ workspace,
455
+ repoRoot: null,
456
+ isGitRepo: false,
457
+ branch: null,
458
+ headCommit: null,
459
+ headMessage: null,
460
+ upstream: null,
461
+ upstreamStatus: "unavailable",
462
+ ahead: 0,
463
+ behind: 0,
464
+ staged: 0,
465
+ modified: 0,
466
+ untracked: 0,
467
+ deleted: 0,
468
+ renamed: 0,
469
+ hasConflicts: false,
470
+ conflictFiles: [],
471
+ stashCount: 0,
472
+ lastCheckedAt,
473
+ error: error.stderr || error.message,
474
+ reason: error.reason
475
+ };
476
+ }
477
+ async function getSubmoduleStatuses(repo, options) {
478
+ if (!repo.repoRoot) return [];
479
+ try {
480
+ const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
481
+ return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
482
+ } catch {
483
+ return [];
484
+ }
485
+ }
486
+ function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
487
+ const submodules = [];
488
+ const ignoreSet = new Set(ignorePaths || []);
489
+ for (const line of output.split("\n")) {
490
+ if (!line.trim()) continue;
491
+ const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
492
+ if (!match) continue;
493
+ const prefix = match[1];
494
+ const commit = match[2];
495
+ const path40 = match[3];
496
+ if (ignoreSet.has(path40)) continue;
497
+ submodules.push({
498
+ path: path40,
499
+ commit,
500
+ repoPath: repoRoot + "/" + path40,
501
+ dirty: prefix === "+",
502
+ outOfSync: prefix === "-",
503
+ lastCheckedAt: Date.now()
504
+ });
505
+ }
506
+ return submodules;
507
+ }
508
+ var init_git_status = __esm({
509
+ "src/git/git-status.ts"() {
510
+ "use strict";
511
+ init_git_executor();
512
+ }
513
+ });
514
+
258
515
  // src/git/git-worktree.ts
259
516
  var git_worktree_exports = {};
260
517
  __export(git_worktree_exports, {
@@ -780,6 +1037,16 @@ function loadMeshConfig() {
780
1037
  return { meshes: [] };
781
1038
  }
782
1039
  }
1040
+ function normalizeCapabilityTags(value) {
1041
+ if (!Array.isArray(value)) return void 0;
1042
+ const seen = /* @__PURE__ */ new Set();
1043
+ const tags = value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
1044
+ if (seen.has(tag)) return false;
1045
+ seen.add(tag);
1046
+ return true;
1047
+ });
1048
+ return tags.length ? tags : void 0;
1049
+ }
783
1050
  function saveMeshConfig(config) {
784
1051
  const path40 = getMeshConfigPath();
785
1052
  (0, import_fs2.writeFileSync)(path40, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
@@ -1065,6 +1332,7 @@ function addNode(meshId, opts) {
1065
1332
  repoRoot: opts.repoRoot,
1066
1333
  daemonId: opts.daemonId,
1067
1334
  machineId: opts.machineId,
1335
+ capabilities: normalizeCapabilityTags(opts.capabilities),
1068
1336
  userOverrides: opts.userOverrides || {},
1069
1337
  policy: opts.policy || {},
1070
1338
  isLocalWorktree: opts.isLocalWorktree,
@@ -2550,6 +2818,369 @@ var init_mesh_ledger = __esm({
2550
2818
  }
2551
2819
  });
2552
2820
 
2821
+ // src/mesh/mesh-fast-forward.ts
2822
+ async function fastForwardMeshNode(args) {
2823
+ const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
2824
+ const nodeId = normalizeOptionalString(args.nodeId);
2825
+ const meshId = normalizeOptionalString(args.meshId);
2826
+ const requestedBranch = normalizeOptionalString(args.branch);
2827
+ const trigger = normalizeOptionalString(args.trigger) || "manual";
2828
+ const updateSubmodules = args.updateSubmodules === true;
2829
+ const dryRun = args.dryRun === true || args.execute !== true;
2830
+ const plannedSteps = buildPlannedSteps(updateSubmodules);
2831
+ const base = {
2832
+ ...nodeId ? { nodeId } : {},
2833
+ ...meshId ? { meshId } : {},
2834
+ workspace,
2835
+ dryRun,
2836
+ updateSubmodules,
2837
+ plannedSteps,
2838
+ trigger
2839
+ };
2840
+ if (!workspace) {
2841
+ return block(base, "invalid_workspace", ["workspace_required"]);
2842
+ }
2843
+ const current = await getGitRepoStatus(workspace, {
2844
+ ...STATUS_OPTIONS,
2845
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
2846
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
2847
+ });
2848
+ const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
2849
+ if (earlyBlockers.length > 0) {
2850
+ const result2 = {
2851
+ ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
2852
+ current,
2853
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
2854
+ };
2855
+ await appendFastForwardLedger(result2, "blocked");
2856
+ return result2;
2857
+ }
2858
+ if (current.behind === 0) {
2859
+ const result2 = {
2860
+ ...base,
2861
+ success: true,
2862
+ code: "already_up_to_date",
2863
+ allowed: true,
2864
+ willRun: false,
2865
+ executed: false,
2866
+ blockingReasons: [],
2867
+ current,
2868
+ preStatus: current,
2869
+ postStatus: current,
2870
+ finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
2871
+ };
2872
+ await appendFastForwardLedger(result2, "noop");
2873
+ return result2;
2874
+ }
2875
+ const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
2876
+ if (!ancestorCheck.ok) {
2877
+ const result2 = {
2878
+ ...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
2879
+ current,
2880
+ preStatus: current,
2881
+ operationError: ancestorCheck.error,
2882
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
2883
+ };
2884
+ await appendFastForwardLedger(result2, "blocked");
2885
+ return result2;
2886
+ }
2887
+ if (dryRun) {
2888
+ const result2 = {
2889
+ ...base,
2890
+ success: true,
2891
+ code: "fast_forward_available",
2892
+ allowed: true,
2893
+ willRun: false,
2894
+ executed: false,
2895
+ blockingReasons: [],
2896
+ current,
2897
+ preStatus: current,
2898
+ finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
2899
+ };
2900
+ await appendFastForwardLedger(result2, "dry_run");
2901
+ return result2;
2902
+ }
2903
+ try {
2904
+ await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
2905
+ } catch (error) {
2906
+ const result2 = {
2907
+ ...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
2908
+ current,
2909
+ preStatus: current,
2910
+ operationError: formatGitError2(error),
2911
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
2912
+ };
2913
+ await appendFastForwardLedger(result2, "failed");
2914
+ return result2;
2915
+ }
2916
+ let postStatus = await getGitRepoStatus(workspace, {
2917
+ ...STATUS_OPTIONS,
2918
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
2919
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
2920
+ });
2921
+ const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
2922
+ let submoduleFollowUpRequired = false;
2923
+ let operationError;
2924
+ if (submoduleIssues.length > 0) {
2925
+ if (updateSubmodules) {
2926
+ try {
2927
+ await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
2928
+ postStatus = await getGitRepoStatus(workspace, {
2929
+ ...STATUS_OPTIONS,
2930
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
2931
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
2932
+ });
2933
+ } catch (error) {
2934
+ operationError = formatGitError2(error);
2935
+ }
2936
+ } else {
2937
+ submoduleFollowUpRequired = true;
2938
+ }
2939
+ }
2940
+ const postBlockers = collectPostExecutionBlockers(postStatus);
2941
+ if (operationError) postBlockers.push("submodule_update_failed");
2942
+ if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
2943
+ const success = postBlockers.length === 0 || submoduleFollowUpRequired;
2944
+ const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
2945
+ const result = {
2946
+ ...base,
2947
+ success,
2948
+ code,
2949
+ allowed: true,
2950
+ willRun: true,
2951
+ executed: true,
2952
+ blockingReasons: postBlockers,
2953
+ current,
2954
+ preStatus: current,
2955
+ postStatus,
2956
+ ...operationError ? { operationError } : {},
2957
+ finalBranchConvergenceState: buildConvergenceState(
2958
+ postStatus,
2959
+ postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
2960
+ )
2961
+ };
2962
+ await appendFastForwardLedger(result, success ? "executed" : "failed");
2963
+ return result;
2964
+ }
2965
+ function buildPlannedSteps(updateSubmodules) {
2966
+ const steps = [
2967
+ {
2968
+ operation: "refresh_upstream",
2969
+ description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
2970
+ safe: true,
2971
+ willMutateWorktree: false
2972
+ },
2973
+ {
2974
+ operation: "verify_clean_worktree",
2975
+ description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
2976
+ safe: true,
2977
+ willMutateWorktree: false
2978
+ },
2979
+ {
2980
+ operation: "verify_fast_forward",
2981
+ description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
2982
+ safe: true,
2983
+ willMutateWorktree: false
2984
+ },
2985
+ {
2986
+ operation: "merge_ff_only",
2987
+ description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
2988
+ safe: true,
2989
+ willMutateWorktree: true
2990
+ }
2991
+ ];
2992
+ if (updateSubmodules) {
2993
+ steps.push({
2994
+ operation: "submodule_update",
2995
+ description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
2996
+ safe: true,
2997
+ willMutateWorktree: true
2998
+ });
2999
+ }
3000
+ steps.push({
3001
+ operation: "verify_post_status",
3002
+ description: "Re-read daemon-owned git status and report final branch convergence state.",
3003
+ safe: true,
3004
+ willMutateWorktree: false
3005
+ });
3006
+ return steps;
3007
+ }
3008
+ function collectPreflightBlockers(status, requestedBranch) {
3009
+ const blockers = [];
3010
+ if (!status.isGitRepo) blockers.push("not_git_repo");
3011
+ if (!status.branch) blockers.push("detached_head_or_unknown_branch");
3012
+ if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
3013
+ if (!status.upstream) blockers.push("upstream_missing");
3014
+ if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
3015
+ if (status.hasConflicts) blockers.push("conflicts_present");
3016
+ if (status.staged > 0) blockers.push("staged_changes_present");
3017
+ if (status.modified > 0) blockers.push("modified_changes_present");
3018
+ if (status.untracked > 0) blockers.push("untracked_changes_present");
3019
+ if (status.deleted > 0) blockers.push("deleted_changes_present");
3020
+ if (status.renamed > 0) blockers.push("renamed_changes_present");
3021
+ if (status.stashCount > 0) blockers.push("stash_entries_present");
3022
+ blockers.push(...collectSubmoduleBlockers(status, "pre"));
3023
+ if (status.ahead > 0 && status.behind > 0) {
3024
+ blockers.push("branch_diverged_from_upstream");
3025
+ blockers.push("branch_has_local_commits");
3026
+ } else if (status.ahead > 0) blockers.push("branch_has_local_commits");
3027
+ return blockers;
3028
+ }
3029
+ function collectPostExecutionBlockers(status) {
3030
+ const blockers = [];
3031
+ if (!status.isGitRepo) blockers.push("post_not_git_repo");
3032
+ if (status.hasConflicts) blockers.push("post_conflicts_present");
3033
+ if (status.ahead !== 0) blockers.push("post_branch_ahead");
3034
+ if (status.behind !== 0) blockers.push("post_branch_still_behind");
3035
+ if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
3036
+ blockers.push("post_working_tree_not_clean");
3037
+ }
3038
+ if (status.stashCount > 0) blockers.push("post_stash_entries_present");
3039
+ blockers.push(...collectSubmoduleBlockers(status, "post"));
3040
+ return blockers;
3041
+ }
3042
+ function collectSubmoduleBlockers(status, phase) {
3043
+ const submodules = Array.isArray(status.submodules) ? status.submodules : [];
3044
+ const blockers = [];
3045
+ for (const submodule of submodules) {
3046
+ if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
3047
+ if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
3048
+ if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
3049
+ }
3050
+ return blockers;
3051
+ }
3052
+ function chooseBlockCode(status, blockers) {
3053
+ if (blockers.includes("not_git_repo")) return "not_git_repo";
3054
+ if (blockers.includes("branch_mismatch")) return "branch_mismatch";
3055
+ if (blockers.includes("upstream_missing")) return "upstream_missing";
3056
+ if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
3057
+ if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
3058
+ if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
3059
+ if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
3060
+ if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
3061
+ return "preflight_blocked";
3062
+ }
3063
+ function codeToConvergenceStatus(code) {
3064
+ if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
3065
+ if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
3066
+ return "blocked";
3067
+ }
3068
+ async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
3069
+ if (!upstream) return { ok: false, error: "missing upstream" };
3070
+ try {
3071
+ await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
3072
+ return { ok: true };
3073
+ } catch (error) {
3074
+ return { ok: false, error: formatGitError2(error) };
3075
+ }
3076
+ }
3077
+ function block(base, code, blockingReasons) {
3078
+ const normalizedReasons = normalizeBlockingReasons(blockingReasons);
3079
+ return {
3080
+ ...base,
3081
+ success: false,
3082
+ code,
3083
+ allowed: false,
3084
+ willRun: false,
3085
+ executed: false,
3086
+ blockingReasons: normalizedReasons
3087
+ };
3088
+ }
3089
+ function normalizeBlockingReasons(reasons) {
3090
+ const normalized = /* @__PURE__ */ new Set();
3091
+ for (const reason of reasons) {
3092
+ normalized.add(reason);
3093
+ }
3094
+ if ([
3095
+ "conflicts_present",
3096
+ "staged_changes_present",
3097
+ "modified_changes_present",
3098
+ "untracked_changes_present",
3099
+ "deleted_changes_present",
3100
+ "renamed_changes_present"
3101
+ ].some((reason) => normalized.has(reason))) {
3102
+ normalized.add("working_tree_not_clean");
3103
+ }
3104
+ return Array.from(normalized);
3105
+ }
3106
+ function buildConvergenceState(status, convergenceStatus) {
3107
+ return {
3108
+ status: convergenceStatus,
3109
+ branch: status.branch,
3110
+ headCommit: status.headCommit,
3111
+ upstream: status.upstream,
3112
+ ahead: status.ahead,
3113
+ behind: status.behind,
3114
+ dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
3115
+ stashCount: status.stashCount,
3116
+ submodules: summarizeSubmodules(status.submodules)
3117
+ };
3118
+ }
3119
+ function summarizeSubmodules(submodules) {
3120
+ return (submodules || []).map((submodule) => ({
3121
+ path: submodule.path,
3122
+ commit: submodule.commit,
3123
+ dirty: submodule.dirty,
3124
+ outOfSync: submodule.outOfSync,
3125
+ ...submodule.error ? { error: submodule.error } : {}
3126
+ }));
3127
+ }
3128
+ function normalizeOptionalString(value) {
3129
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
3130
+ }
3131
+ function formatGitError2(error) {
3132
+ if (error instanceof GitCommandError) {
3133
+ return error.stderr || error.stdout || error.message;
3134
+ }
3135
+ if (error instanceof Error) return error.message;
3136
+ return String(error);
3137
+ }
3138
+ async function appendFastForwardLedger(result, outcome) {
3139
+ if (!result.meshId) return;
3140
+ try {
3141
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
3142
+ appendLedgerEntry2(result.meshId, {
3143
+ kind: "direct_fast_forward",
3144
+ ...result.nodeId ? { nodeId: result.nodeId } : {},
3145
+ payload: {
3146
+ operation: "mesh_fast_forward_node",
3147
+ trigger: result.trigger || "manual",
3148
+ outcome,
3149
+ code: result.code,
3150
+ workspace: result.workspace,
3151
+ allowed: result.allowed,
3152
+ dryRun: result.dryRun,
3153
+ willRun: result.willRun,
3154
+ executed: result.executed,
3155
+ branch: result.postStatus?.branch ?? result.current?.branch,
3156
+ upstream: result.postStatus?.upstream ?? result.current?.upstream,
3157
+ before: result.current ? {
3158
+ headCommit: result.current.headCommit,
3159
+ ahead: result.current.ahead,
3160
+ behind: result.current.behind
3161
+ } : void 0,
3162
+ after: result.postStatus ? {
3163
+ headCommit: result.postStatus.headCommit,
3164
+ ahead: result.postStatus.ahead,
3165
+ behind: result.postStatus.behind
3166
+ } : void 0,
3167
+ blockingReasons: result.blockingReasons
3168
+ }
3169
+ });
3170
+ } catch (error) {
3171
+ result.ledgerError = error instanceof Error ? error.message : String(error);
3172
+ }
3173
+ }
3174
+ var STATUS_OPTIONS;
3175
+ var init_mesh_fast_forward = __esm({
3176
+ "src/mesh/mesh-fast-forward.ts"() {
3177
+ "use strict";
3178
+ init_git_status();
3179
+ init_git_executor();
3180
+ STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
3181
+ }
3182
+ });
3183
+
2553
3184
  // src/mesh/beads-db.ts
2554
3185
  function loadDatabaseCtor() {
2555
3186
  if (DatabaseCtor) return DatabaseCtor;
@@ -2571,6 +3202,7 @@ var init_beads_db = __esm({
2571
3202
  import_path7 = require("path");
2572
3203
  import_module = require("module");
2573
3204
  init_mesh_ledger();
3205
+ init_mesh_work_queue();
2574
3206
  import_meta = {};
2575
3207
  BeadsDB = class _BeadsDB {
2576
3208
  static instance;
@@ -2792,25 +3424,29 @@ var init_beads_db = __esm({
2792
3424
  return row !== void 0;
2793
3425
  }
2794
3426
  // O(1) claim: transaction ensures only one session claims a pending task
2795
- claimNextQueueTask(meshId, nodeId, sessionId) {
3427
+ claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags = []) {
2796
3428
  return this.transaction(() => {
2797
3429
  this.ensureLegacyQueueMigrated(meshId);
2798
3430
  if (this.hasActiveAssignment(meshId, sessionId, nodeId)) return null;
2799
- const row = this.db.prepare(`
3431
+ const rows = [
3432
+ ...this.db.prepare(`
2800
3433
  SELECT payload FROM mesh_queue
2801
3434
  WHERE mesh_id = ? AND status = 'pending' AND target_session_id = ?
2802
- ORDER BY created_at ASC LIMIT 1
2803
- `).get(meshId, sessionId) || this.db.prepare(`
3435
+ ORDER BY created_at ASC
3436
+ `).all(meshId, sessionId),
3437
+ ...this.db.prepare(`
2804
3438
  SELECT payload FROM mesh_queue
2805
3439
  WHERE mesh_id = ? AND status = 'pending' AND target_node_id = ? AND target_session_id IS NULL
2806
- ORDER BY created_at ASC LIMIT 1
2807
- `).get(meshId, nodeId) || this.db.prepare(`
3440
+ ORDER BY created_at ASC
3441
+ `).all(meshId, nodeId),
3442
+ ...this.db.prepare(`
2808
3443
  SELECT payload FROM mesh_queue
2809
3444
  WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
2810
- ORDER BY created_at ASC LIMIT 1
2811
- `).get(meshId);
2812
- if (!row) return null;
2813
- const entry = JSON.parse(row.payload);
3445
+ ORDER BY created_at ASC
3446
+ `).all(meshId)
3447
+ ];
3448
+ const entry = rows.map((row) => JSON.parse(row.payload)).find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags));
3449
+ if (!entry) return null;
2814
3450
  const now = (/* @__PURE__ */ new Date()).toISOString();
2815
3451
  entry.status = "assigned";
2816
3452
  entry.assignedNodeId = nodeId;
@@ -2958,6 +3594,7 @@ __export(mesh_work_queue_exports, {
2958
3594
  __clearMeshQueueForTests: () => __clearMeshQueueForTests,
2959
3595
  __replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
2960
3596
  __resetBeadsDBForTests: () => __resetBeadsDBForTests,
3597
+ buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
2961
3598
  cancelTask: () => cancelTask,
2962
3599
  claimNextTask: () => claimNextTask,
2963
3600
  cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
@@ -2968,6 +3605,8 @@ __export(mesh_work_queue_exports, {
2968
3605
  getQueue: () => getQueue,
2969
3606
  insertDirectDispatch: () => insertDirectDispatch,
2970
3607
  markStaleDirectDispatches: () => markStaleDirectDispatches,
3608
+ nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
3609
+ normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
2971
3610
  normalizeMeshTaskMode: () => normalizeMeshTaskMode,
2972
3611
  recordTaskAutoLaunch: () => recordTaskAutoLaunch,
2973
3612
  requeueTask: () => requeueTask,
@@ -3002,6 +3641,35 @@ function validateMeshTaskModeRequest(mode, message) {
3002
3641
  ]
3003
3642
  };
3004
3643
  }
3644
+ function normalizeMeshCapabilityTags(value) {
3645
+ if (!Array.isArray(value)) return [];
3646
+ const seen = /* @__PURE__ */ new Set();
3647
+ return value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
3648
+ if (seen.has(tag)) return false;
3649
+ seen.add(tag);
3650
+ return true;
3651
+ });
3652
+ }
3653
+ function firstProviderPriority(policy) {
3654
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
3655
+ if (!Array.isArray(raw)) return void 0;
3656
+ return raw.find((type) => typeof type === "string" && type.trim())?.trim();
3657
+ }
3658
+ function buildMeshNodeCapabilityTags(node, providerType) {
3659
+ const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
3660
+ return normalizeMeshCapabilityTags([
3661
+ ...Array.isArray(node?.capabilities) ? node.capabilities : [],
3662
+ `os=${process.platform}`,
3663
+ `arch=${process.arch}`,
3664
+ ...provider ? [`provider=${provider}`] : []
3665
+ ]);
3666
+ }
3667
+ function nodeSatisfiesRequiredTags(requiredTags, capabilityTags) {
3668
+ const required = normalizeMeshCapabilityTags(requiredTags);
3669
+ if (required.length === 0) return true;
3670
+ const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
3671
+ return required.every((tag) => available.has(tag));
3672
+ }
3005
3673
  function withQueueLock(_meshId, fn) {
3006
3674
  return BeadsDB.getInstance().transaction(fn);
3007
3675
  }
@@ -3019,6 +3687,7 @@ function enqueueTask(meshId, message, opts) {
3019
3687
  taskMode: modeValidation.taskMode,
3020
3688
  targetNodeId: opts?.targetNodeId,
3021
3689
  targetSessionId: opts?.targetSessionId,
3690
+ requiredTags: normalizeMeshCapabilityTags(opts?.requiredTags),
3022
3691
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3023
3692
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3024
3693
  };
@@ -3031,8 +3700,8 @@ function getQueue(meshId, opts) {
3031
3700
  function getMeshQueueRevision(meshId) {
3032
3701
  return BeadsDB.getInstance().getQueueRevision(meshId);
3033
3702
  }
3034
- function claimNextTask(meshId, nodeId, sessionId) {
3035
- return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId);
3703
+ function claimNextTask(meshId, nodeId, sessionId, capabilityTags) {
3704
+ return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags);
3036
3705
  }
3037
3706
  function updateTaskStatus(meshId, taskId, status, opts) {
3038
3707
  requireMeshHostQueueOwner(opts);
@@ -3325,6 +3994,7 @@ var init_cli_detector = __esm({
3325
3994
  // src/mesh/mesh-events.ts
3326
3995
  var mesh_events_exports = {};
3327
3996
  __export(mesh_events_exports, {
3997
+ __resetIdleAutoFastForwardForTests: () => __resetIdleAutoFastForwardForTests,
3328
3998
  clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
3329
3999
  drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
3330
4000
  getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
@@ -3345,6 +4015,9 @@ function getCachedMeshByWorkspace(workspace) {
3345
4015
  function readWorkerResultMetadata(event) {
3346
4016
  return readRecord2(event.workerResult) || readRecord2(event.meshWorkerResult) || readRecord2(event.structuredResult);
3347
4017
  }
4018
+ function __resetIdleAutoFastForwardForTests() {
4019
+ idleAutoFastForwardLastAttempt.clear();
4020
+ }
3348
4021
  function sweepExpiredRemoteIdleSessions() {
3349
4022
  const now = Date.now();
3350
4023
  for (const [key, session] of remoteIdleSessions) {
@@ -3770,13 +4443,14 @@ function buildLongGeneratingCompletionReconciliation(args) {
3770
4443
  };
3771
4444
  }
3772
4445
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
3773
- const task = claimNextTask(meshId, nodeId, sessionId);
4446
+ const mesh = getMeshWithCache(components, meshId);
4447
+ const node = mesh?.nodes.find((n) => n.id === nodeId);
4448
+ const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
4449
+ const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags);
3774
4450
  if (!task) {
3775
4451
  return false;
3776
4452
  }
3777
4453
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
3778
- const mesh = getMeshWithCache(components, meshId);
3779
- const node = mesh?.nodes.find((n) => n.id === nodeId);
3780
4454
  if (node?.daemonId && components.dispatchMeshCommand) {
3781
4455
  const isLocalNode = components.cliManager.adapters.has(sessionId);
3782
4456
  if (!isLocalNode) {
@@ -4082,6 +4756,55 @@ async function triggerMeshQueue(components, meshId) {
4082
4756
  }
4083
4757
  await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
4084
4758
  }
4759
+ async function maybeAutoFastForwardIdleNode(components, args) {
4760
+ const mesh = getMeshWithCache(components, args.meshId);
4761
+ const node = mesh?.nodes?.find((candidate) => candidate?.id === args.nodeId || candidate?.nodeId === args.nodeId);
4762
+ const workspace = readNonEmptyString2(node?.workspace);
4763
+ if (!workspace) return;
4764
+ if (!(0, import_fs9.existsSync)(workspace)) return;
4765
+ const throttleKey = `${args.meshId}:${args.nodeId}`;
4766
+ const now = Date.now();
4767
+ const lastAttempt = idleAutoFastForwardLastAttempt.get(throttleKey) || 0;
4768
+ if (now - lastAttempt < IDLE_AUTO_FAST_FORWARD_THROTTLE_MS) return;
4769
+ idleAutoFastForwardLastAttempt.set(throttleKey, now);
4770
+ const submoduleIgnorePaths = Array.isArray(node?.policy?.submoduleIgnorePaths) ? node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
4771
+ try {
4772
+ const dryRun = await fastForwardMeshNode({
4773
+ meshId: args.meshId,
4774
+ nodeId: args.nodeId,
4775
+ workspace,
4776
+ execute: false,
4777
+ dryRun: true,
4778
+ updateSubmodules: false,
4779
+ submoduleIgnorePaths,
4780
+ trigger: "idle_auto"
4781
+ });
4782
+ if (!dryRun || dryRun.code !== "fast_forward_available" || dryRun.allowed !== true) return;
4783
+ await fastForwardMeshNode({
4784
+ meshId: args.meshId,
4785
+ nodeId: args.nodeId,
4786
+ workspace,
4787
+ execute: true,
4788
+ dryRun: false,
4789
+ updateSubmodules: false,
4790
+ submoduleIgnorePaths,
4791
+ trigger: "idle_auto"
4792
+ });
4793
+ } catch (e) {
4794
+ LOG.warn("MeshFastForward", `Idle auto fast-forward check failed for ${args.nodeId}: ${e?.message || e}`);
4795
+ }
4796
+ }
4797
+ function runIdleMaintenanceThenAssignQueue(components, args) {
4798
+ setImmediate(() => {
4799
+ maybeAutoFastForwardIdleNode(components, args).finally(() => {
4800
+ try {
4801
+ tryAssignQueueTask(components, args.meshId, args.nodeId, args.sessionId, args.providerType);
4802
+ } catch (e) {
4803
+ LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${args.nodeId}: ${e?.message || e}`);
4804
+ }
4805
+ });
4806
+ });
4807
+ }
4085
4808
  function buildMeshSystemMessage(args) {
4086
4809
  const metadata = formatCompletionMetadata(args.metadataEvent);
4087
4810
  if (args.event === "agent:generating_completed") {
@@ -4303,9 +5026,7 @@ function injectMeshSystemMessage(components, args) {
4303
5026
  updateDirectDispatchStatus(args.meshId, sessionId, "completed");
4304
5027
  setImmediate(() => cleanupTerminalDirectDispatches());
4305
5028
  if (nodeId && providerType) {
4306
- setImmediate(() => {
4307
- tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
4308
- });
5029
+ runIdleMaintenanceThenAssignQueue(components, { meshId: args.meshId, nodeId, sessionId, providerType });
4309
5030
  }
4310
5031
  }
4311
5032
  } else if (args.event === "agent:ready") {
@@ -4359,8 +5080,14 @@ function injectMeshSystemMessage(components, args) {
4359
5080
  expiresAt: Date.now() + REMOTE_IDLE_SESSION_TTL_MS
4360
5081
  });
4361
5082
  setImmediate(() => {
4362
- const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
4363
- if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
5083
+ maybeAutoFastForwardIdleNode(components, { meshId: args.meshId, nodeId, sessionId, providerType }).finally(() => {
5084
+ try {
5085
+ const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
5086
+ if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
5087
+ } catch (e) {
5088
+ LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${nodeId}: ${e?.message || e}`);
5089
+ }
5090
+ });
4364
5091
  });
4365
5092
  }
4366
5093
  } else if (args.event === "agent:generating_started") {
@@ -4636,7 +5363,7 @@ function setupMeshEventForwarding(components) {
4636
5363
  });
4637
5364
  });
4638
5365
  }
4639
- var import_fs9, import_path8, REMOTE_IDLE_SESSION_TTL_MS, remoteIdleSessions, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, REFINE_TERMINAL_EVENTS, MAX_PENDING_EVENTS_BYTES, MAX_PENDING_EVENTS_KEEP, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
5366
+ var import_fs9, import_path8, REMOTE_IDLE_SESSION_TTL_MS, remoteIdleSessions, meshByWorkspaceCache, MESH_WORKSPACE_CACHE_TTL_MS, IDLE_AUTO_FAST_FORWARD_THROTTLE_MS, idleAutoFastForwardLastAttempt, REFINE_TERMINAL_EVENTS, MAX_PENDING_EVENTS_BYTES, MAX_PENDING_EVENTS_KEEP, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, RECENT_COMPLETION_FINGERPRINT_TTL_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
4640
5367
  var init_mesh_events = __esm({
4641
5368
  "src/mesh/mesh-events.ts"() {
4642
5369
  "use strict";
@@ -4649,10 +5376,13 @@ var init_mesh_events = __esm({
4649
5376
  init_mesh_ledger();
4650
5377
  init_mesh_work_queue();
4651
5378
  init_beads_db();
5379
+ init_mesh_fast_forward();
4652
5380
  REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1e3;
4653
5381
  remoteIdleSessions = /* @__PURE__ */ new Map();
4654
5382
  meshByWorkspaceCache = /* @__PURE__ */ new Map();
4655
5383
  MESH_WORKSPACE_CACHE_TTL_MS = 5e3;
5384
+ IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
5385
+ idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
4656
5386
  REFINE_TERMINAL_EVENTS = /* @__PURE__ */ new Set(["refine:completed", "refine:failed"]);
4657
5387
  MAX_PENDING_EVENTS_BYTES = 100 * 1024;
4658
5388
  MAX_PENDING_EVENTS_KEEP = 50;
@@ -12143,6 +12873,7 @@ __export(index_exports, {
12143
12873
  buildMeshHostRequiredFailure: () => buildMeshHostRequiredFailure,
12144
12874
  buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
12145
12875
  buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
12876
+ buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
12146
12877
  buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
12147
12878
  buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
12148
12879
  buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
@@ -12270,6 +13001,7 @@ __export(index_exports, {
12270
13001
  maybeRunDaemonUpgradeHelperFromEnv: () => maybeRunDaemonUpgradeHelperFromEnv,
12271
13002
  namedKeyToAnsi: () => namedKeyToAnsi,
12272
13003
  namedKeysToAnsi: () => namedKeysToAnsi,
13004
+ nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
12273
13005
  normalizeActiveChatData: () => normalizeActiveChatData,
12274
13006
  normalizeChatMessage: () => normalizeChatMessage,
12275
13007
  normalizeChatMessageKind: () => normalizeChatMessageKind,
@@ -12281,6 +13013,7 @@ __export(index_exports, {
12281
13013
  normalizeInteractivePrompt: () => normalizeInteractivePrompt,
12282
13014
  normalizeInteractivePromptResponse: () => normalizeInteractivePromptResponse,
12283
13015
  normalizeManagedStatus: () => normalizeManagedStatus,
13016
+ normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
12284
13017
  normalizeMeshDaemonRole: () => normalizeMeshDaemonRole,
12285
13018
  normalizeMeshTaskMode: () => normalizeMeshTaskMode,
12286
13019
  normalizeMeshWorkerResult: () => normalizeMeshWorkerResult,
@@ -12339,7 +13072,6 @@ __export(index_exports, {
12339
13072
  startLocalIpcServer: () => startLocalIpcServer,
12340
13073
  suggestMeshRefineConfig: () => suggestMeshRefineConfig,
12341
13074
  summarizeGitStatus: () => summarizeGitStatus,
12342
- syncMeshes: () => syncMeshes,
12343
13075
  triggerMeshQueue: () => triggerMeshQueue,
12344
13076
  unregisterMeshCoordinator: () => unregisterMeshCoordinator,
12345
13077
  updateConfig: () => updateConfig,
@@ -12582,258 +13314,7 @@ init_repo_mesh_types();
12582
13314
 
12583
13315
  // src/git/index.ts
12584
13316
  init_git_executor();
12585
-
12586
- // src/git/git-status.ts
12587
- init_git_executor();
12588
- async function getGitRepoStatus(workspace, options = {}) {
12589
- const lastCheckedAt = Date.now();
12590
- const includeSubmodules = options.includeSubmodules !== false;
12591
- try {
12592
- const repo = await resolveGitRepository(workspace, options);
12593
- let parsed = await readPorcelainStatus(repo, options);
12594
- let upstreamProbe = getInitialUpstreamProbe(parsed);
12595
- if (options.refreshUpstream) {
12596
- upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
12597
- if (upstreamProbe.upstreamStatus === "fresh") {
12598
- parsed = await readPorcelainStatus(repo, options);
12599
- }
12600
- }
12601
- const head = await readHead(repo, options);
12602
- const stashCount = await readStashCount(repo, options);
12603
- let submodules;
12604
- if (includeSubmodules) {
12605
- submodules = await getSubmoduleStatuses(repo, options);
12606
- }
12607
- return {
12608
- workspace: repo.workspace,
12609
- repoRoot: repo.repoRoot,
12610
- isGitRepo: true,
12611
- branch: parsed.branch,
12612
- headCommit: head.commit,
12613
- headMessage: head.message,
12614
- upstream: parsed.upstream,
12615
- upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
12616
- upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
12617
- upstreamFetchError: upstreamProbe.upstreamFetchError,
12618
- ahead: parsed.ahead,
12619
- behind: parsed.behind,
12620
- staged: parsed.staged,
12621
- modified: parsed.modified,
12622
- untracked: parsed.untracked,
12623
- deleted: parsed.deleted,
12624
- renamed: parsed.renamed,
12625
- hasConflicts: parsed.conflictFiles.length > 0,
12626
- conflictFiles: parsed.conflictFiles,
12627
- stashCount,
12628
- lastCheckedAt,
12629
- submodules
12630
- };
12631
- } catch (error) {
12632
- if (error instanceof GitCommandError) {
12633
- return emptyStatus(workspace, lastCheckedAt, error);
12634
- }
12635
- return emptyStatus(
12636
- workspace,
12637
- lastCheckedAt,
12638
- new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
12639
- );
12640
- }
12641
- }
12642
- async function readPorcelainStatus(repo, options) {
12643
- const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
12644
- return parsePorcelainV2Status(statusOutput.stdout);
12645
- }
12646
- function getInitialUpstreamProbe(parsed) {
12647
- return {
12648
- upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
12649
- };
12650
- }
12651
- async function refreshTrackedUpstream(repo, parsed, options) {
12652
- if (!parsed.upstream || !parsed.branch) {
12653
- return { upstreamStatus: "no_upstream" };
12654
- }
12655
- const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
12656
- if (!remoteName) {
12657
- return {
12658
- upstreamStatus: "stale",
12659
- upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
12660
- };
12661
- }
12662
- try {
12663
- await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
12664
- return {
12665
- upstreamStatus: "fresh",
12666
- upstreamFetchedAt: Date.now()
12667
- };
12668
- } catch (error) {
12669
- return {
12670
- upstreamStatus: "stale",
12671
- upstreamFetchError: formatGitError(error)
12672
- };
12673
- }
12674
- }
12675
- async function readBranchRemote(repo, branch, options) {
12676
- try {
12677
- const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
12678
- return result.stdout.trim() || null;
12679
- } catch {
12680
- return null;
12681
- }
12682
- }
12683
- function inferRemoteName(upstream) {
12684
- const [remoteName] = upstream.split("/");
12685
- return remoteName?.trim() || null;
12686
- }
12687
- function formatGitError(error) {
12688
- if (error instanceof GitCommandError) {
12689
- return error.stderr || error.message;
12690
- }
12691
- if (error instanceof Error) {
12692
- return error.message;
12693
- }
12694
- return String(error);
12695
- }
12696
- function parsePorcelainV2Status(output) {
12697
- const parsed = {
12698
- branch: null,
12699
- upstream: null,
12700
- ahead: 0,
12701
- behind: 0,
12702
- staged: 0,
12703
- modified: 0,
12704
- untracked: 0,
12705
- deleted: 0,
12706
- renamed: 0,
12707
- conflictFiles: []
12708
- };
12709
- for (const line of output.split("\n")) {
12710
- if (!line) continue;
12711
- if (line.startsWith("# branch.head ")) {
12712
- const branch = line.slice("# branch.head ".length).trim();
12713
- parsed.branch = branch && branch !== "(detached)" ? branch : null;
12714
- continue;
12715
- }
12716
- if (line.startsWith("# branch.upstream ")) {
12717
- parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
12718
- continue;
12719
- }
12720
- if (line.startsWith("# branch.ab ")) {
12721
- const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
12722
- if (match) {
12723
- parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
12724
- parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
12725
- }
12726
- continue;
12727
- }
12728
- if (line.startsWith("? ")) {
12729
- parsed.untracked += 1;
12730
- continue;
12731
- }
12732
- if (line.startsWith("u ")) {
12733
- const fields = line.split(" ");
12734
- const filePath = fields.slice(10).join(" ");
12735
- if (filePath) parsed.conflictFiles.push(filePath);
12736
- continue;
12737
- }
12738
- if (line.startsWith("1 ") || line.startsWith("2 ")) {
12739
- const fields = line.split(" ");
12740
- const xy = fields[1] ?? "..";
12741
- const indexStatus = xy[0] ?? ".";
12742
- const worktreeStatus = xy[1] ?? ".";
12743
- if (isStagedStatus(indexStatus)) parsed.staged += 1;
12744
- if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
12745
- if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
12746
- if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
12747
- if (xy.includes("U")) {
12748
- const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
12749
- if (filePath) parsed.conflictFiles.push(filePath);
12750
- }
12751
- }
12752
- }
12753
- parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
12754
- return parsed;
12755
- }
12756
- async function readHead(repo, options) {
12757
- try {
12758
- const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
12759
- const text = result.stdout.trimEnd();
12760
- if (!text) return { commit: null, message: null };
12761
- const [commit, ...messageParts] = text.split("\0");
12762
- return {
12763
- commit: commit || null,
12764
- message: messageParts.join("\0") || null
12765
- };
12766
- } catch {
12767
- return { commit: null, message: null };
12768
- }
12769
- }
12770
- async function readStashCount(repo, options) {
12771
- try {
12772
- const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
12773
- return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
12774
- } catch {
12775
- return 0;
12776
- }
12777
- }
12778
- function isStagedStatus(status) {
12779
- return status !== "." && status !== "?" && status !== "U";
12780
- }
12781
- function emptyStatus(workspace, lastCheckedAt, error) {
12782
- return {
12783
- workspace,
12784
- repoRoot: null,
12785
- isGitRepo: false,
12786
- branch: null,
12787
- headCommit: null,
12788
- headMessage: null,
12789
- upstream: null,
12790
- upstreamStatus: "unavailable",
12791
- ahead: 0,
12792
- behind: 0,
12793
- staged: 0,
12794
- modified: 0,
12795
- untracked: 0,
12796
- deleted: 0,
12797
- renamed: 0,
12798
- hasConflicts: false,
12799
- conflictFiles: [],
12800
- stashCount: 0,
12801
- lastCheckedAt,
12802
- error: error.stderr || error.message,
12803
- reason: error.reason
12804
- };
12805
- }
12806
- async function getSubmoduleStatuses(repo, options) {
12807
- if (!repo.repoRoot) return [];
12808
- try {
12809
- const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
12810
- return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
12811
- } catch {
12812
- return [];
12813
- }
12814
- }
12815
- function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
12816
- const submodules = [];
12817
- const ignoreSet = new Set(ignorePaths || []);
12818
- for (const line of output.split("\n")) {
12819
- if (!line.trim()) continue;
12820
- const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
12821
- if (!match) continue;
12822
- const prefix = match[1];
12823
- const commit = match[2];
12824
- const path40 = match[3];
12825
- if (ignoreSet.has(path40)) continue;
12826
- submodules.push({
12827
- path: path40,
12828
- commit,
12829
- repoPath: repoRoot + "/" + path40,
12830
- dirty: prefix === "+",
12831
- outOfSync: prefix === "-",
12832
- lastCheckedAt: Date.now()
12833
- });
12834
- }
12835
- return submodules;
12836
- }
13317
+ init_git_status();
12837
13318
 
12838
13319
  // src/git/git-diff.ts
12839
13320
  var import_promises2 = require("fs/promises");
@@ -13249,6 +13730,7 @@ function createGitSnapshotStore(options = {}) {
13249
13730
  }
13250
13731
 
13251
13732
  // src/git/git-monitor.ts
13733
+ init_git_status();
13252
13734
  var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
13253
13735
  var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
13254
13736
  function defaultStatusProvider(workspace) {
@@ -13371,6 +13853,7 @@ function createGitWorkspaceMonitor(options = {}) {
13371
13853
  // src/git/git-commands.ts
13372
13854
  var path3 = __toESM(require("path"));
13373
13855
  init_git_executor();
13856
+ init_git_status();
13374
13857
  var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
13375
13858
  "git_status",
13376
13859
  "git_diff_summary",
@@ -14851,414 +15334,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
14851
15334
  return state;
14852
15335
  }
14853
15336
 
14854
- // src/mesh/mesh-sync.ts
14855
- init_mesh_config();
14856
- async function syncMeshes(transport) {
14857
- const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
14858
- let remoteMeshes;
14859
- try {
14860
- const res = await transport.listRemoteMeshes();
14861
- remoteMeshes = res.meshes;
14862
- } catch (e) {
14863
- result.errors.push(`Failed to list remote meshes: ${e.message}`);
14864
- return result;
14865
- }
14866
- const localMeshes = listMeshes();
14867
- const remoteByIdentity = new Map(remoteMeshes.map((m) => [m.repo_identity, m]));
14868
- const localByIdentity = new Map(localMeshes.map((m) => [m.repoIdentity, m]));
14869
- for (const local of localMeshes) {
14870
- if (!remoteByIdentity.has(local.repoIdentity)) {
14871
- try {
14872
- await transport.createRemoteMesh({
14873
- name: local.name,
14874
- repo_identity: local.repoIdentity,
14875
- repo_remote_url: local.repoRemoteUrl,
14876
- default_branch: local.defaultBranch,
14877
- policy: JSON.stringify(local.policy)
14878
- });
14879
- result.pushed++;
14880
- } catch (e) {
14881
- result.errors.push(`Push failed for "${local.name}": ${e.message}`);
14882
- }
14883
- }
14884
- }
14885
- for (const remote of remoteMeshes) {
14886
- if (!localByIdentity.has(remote.repo_identity)) {
14887
- try {
14888
- let policy;
14889
- try {
14890
- policy = JSON.parse(remote.policy);
14891
- } catch {
14892
- policy = void 0;
14893
- }
14894
- createMesh({
14895
- name: remote.name,
14896
- repoIdentity: remote.repo_identity,
14897
- repoRemoteUrl: remote.repo_remote_url || void 0,
14898
- defaultBranch: remote.default_branch || void 0,
14899
- policy
14900
- });
14901
- result.pulled++;
14902
- } catch (e) {
14903
- result.errors.push(`Pull failed for "${remote.name}": ${e.message}`);
14904
- }
14905
- }
14906
- }
14907
- return result;
14908
- }
14909
-
14910
15337
  // src/index.ts
14911
15338
  init_mesh_ledger();
14912
-
14913
- // src/mesh/mesh-fast-forward.ts
14914
- init_git_executor();
14915
- var STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
14916
- async function fastForwardMeshNode(args) {
14917
- const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
14918
- const nodeId = normalizeOptionalString(args.nodeId);
14919
- const meshId = normalizeOptionalString(args.meshId);
14920
- const requestedBranch = normalizeOptionalString(args.branch);
14921
- const updateSubmodules = args.updateSubmodules === true;
14922
- const dryRun = args.dryRun === true || args.execute !== true;
14923
- const plannedSteps = buildPlannedSteps(updateSubmodules);
14924
- const base = {
14925
- ...nodeId ? { nodeId } : {},
14926
- ...meshId ? { meshId } : {},
14927
- workspace,
14928
- dryRun,
14929
- updateSubmodules,
14930
- plannedSteps
14931
- };
14932
- if (!workspace) {
14933
- return block(base, "invalid_workspace", ["workspace_required"]);
14934
- }
14935
- const current = await getGitRepoStatus(workspace, {
14936
- ...STATUS_OPTIONS,
14937
- submoduleIgnorePaths: args.submoduleIgnorePaths,
14938
- timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
14939
- });
14940
- const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
14941
- if (earlyBlockers.length > 0) {
14942
- return {
14943
- ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
14944
- current,
14945
- finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
14946
- };
14947
- }
14948
- if (current.behind === 0) {
14949
- const result2 = {
14950
- ...base,
14951
- success: true,
14952
- code: "already_up_to_date",
14953
- allowed: true,
14954
- willRun: false,
14955
- executed: false,
14956
- blockingReasons: [],
14957
- current,
14958
- preStatus: current,
14959
- postStatus: current,
14960
- finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
14961
- };
14962
- await appendFastForwardLedger(result2, "noop");
14963
- return result2;
14964
- }
14965
- const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
14966
- if (!ancestorCheck.ok) {
14967
- const result2 = {
14968
- ...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
14969
- current,
14970
- preStatus: current,
14971
- operationError: ancestorCheck.error,
14972
- finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
14973
- };
14974
- await appendFastForwardLedger(result2, "blocked");
14975
- return result2;
14976
- }
14977
- if (dryRun) {
14978
- const result2 = {
14979
- ...base,
14980
- success: true,
14981
- code: "fast_forward_available",
14982
- allowed: true,
14983
- willRun: false,
14984
- executed: false,
14985
- blockingReasons: [],
14986
- current,
14987
- preStatus: current,
14988
- finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
14989
- };
14990
- return result2;
14991
- }
14992
- try {
14993
- await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
14994
- } catch (error) {
14995
- const result2 = {
14996
- ...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
14997
- current,
14998
- preStatus: current,
14999
- operationError: formatGitError2(error),
15000
- finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
15001
- };
15002
- await appendFastForwardLedger(result2, "failed");
15003
- return result2;
15004
- }
15005
- let postStatus = await getGitRepoStatus(workspace, {
15006
- ...STATUS_OPTIONS,
15007
- submoduleIgnorePaths: args.submoduleIgnorePaths,
15008
- timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
15009
- });
15010
- const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
15011
- let submoduleFollowUpRequired = false;
15012
- let operationError;
15013
- if (submoduleIssues.length > 0) {
15014
- if (updateSubmodules) {
15015
- try {
15016
- await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
15017
- postStatus = await getGitRepoStatus(workspace, {
15018
- ...STATUS_OPTIONS,
15019
- submoduleIgnorePaths: args.submoduleIgnorePaths,
15020
- timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
15021
- });
15022
- } catch (error) {
15023
- operationError = formatGitError2(error);
15024
- }
15025
- } else {
15026
- submoduleFollowUpRequired = true;
15027
- }
15028
- }
15029
- const postBlockers = collectPostExecutionBlockers(postStatus);
15030
- if (operationError) postBlockers.push("submodule_update_failed");
15031
- if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
15032
- const success = postBlockers.length === 0 || submoduleFollowUpRequired;
15033
- const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
15034
- const result = {
15035
- ...base,
15036
- success,
15037
- code,
15038
- allowed: true,
15039
- willRun: true,
15040
- executed: true,
15041
- blockingReasons: postBlockers,
15042
- current,
15043
- preStatus: current,
15044
- postStatus,
15045
- ...operationError ? { operationError } : {},
15046
- finalBranchConvergenceState: buildConvergenceState(
15047
- postStatus,
15048
- postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
15049
- )
15050
- };
15051
- await appendFastForwardLedger(result, success ? "executed" : "failed");
15052
- return result;
15053
- }
15054
- function buildPlannedSteps(updateSubmodules) {
15055
- const steps = [
15056
- {
15057
- operation: "refresh_upstream",
15058
- description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
15059
- safe: true,
15060
- willMutateWorktree: false
15061
- },
15062
- {
15063
- operation: "verify_clean_worktree",
15064
- description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
15065
- safe: true,
15066
- willMutateWorktree: false
15067
- },
15068
- {
15069
- operation: "verify_fast_forward",
15070
- description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
15071
- safe: true,
15072
- willMutateWorktree: false
15073
- },
15074
- {
15075
- operation: "merge_ff_only",
15076
- description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
15077
- safe: true,
15078
- willMutateWorktree: true
15079
- }
15080
- ];
15081
- if (updateSubmodules) {
15082
- steps.push({
15083
- operation: "submodule_update",
15084
- description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
15085
- safe: true,
15086
- willMutateWorktree: true
15087
- });
15088
- }
15089
- steps.push({
15090
- operation: "verify_post_status",
15091
- description: "Re-read daemon-owned git status and report final branch convergence state.",
15092
- safe: true,
15093
- willMutateWorktree: false
15094
- });
15095
- return steps;
15096
- }
15097
- function collectPreflightBlockers(status, requestedBranch) {
15098
- const blockers = [];
15099
- if (!status.isGitRepo) blockers.push("not_git_repo");
15100
- if (!status.branch) blockers.push("detached_head_or_unknown_branch");
15101
- if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
15102
- if (!status.upstream) blockers.push("upstream_missing");
15103
- if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
15104
- if (status.hasConflicts) blockers.push("conflicts_present");
15105
- if (status.staged > 0) blockers.push("staged_changes_present");
15106
- if (status.modified > 0) blockers.push("modified_changes_present");
15107
- if (status.untracked > 0) blockers.push("untracked_changes_present");
15108
- if (status.deleted > 0) blockers.push("deleted_changes_present");
15109
- if (status.renamed > 0) blockers.push("renamed_changes_present");
15110
- if (status.stashCount > 0) blockers.push("stash_entries_present");
15111
- blockers.push(...collectSubmoduleBlockers(status, "pre"));
15112
- if (status.ahead > 0 && status.behind > 0) {
15113
- blockers.push("branch_diverged_from_upstream");
15114
- blockers.push("branch_has_local_commits");
15115
- } else if (status.ahead > 0) blockers.push("branch_has_local_commits");
15116
- return blockers;
15117
- }
15118
- function collectPostExecutionBlockers(status) {
15119
- const blockers = [];
15120
- if (!status.isGitRepo) blockers.push("post_not_git_repo");
15121
- if (status.hasConflicts) blockers.push("post_conflicts_present");
15122
- if (status.ahead !== 0) blockers.push("post_branch_ahead");
15123
- if (status.behind !== 0) blockers.push("post_branch_still_behind");
15124
- if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
15125
- blockers.push("post_working_tree_not_clean");
15126
- }
15127
- if (status.stashCount > 0) blockers.push("post_stash_entries_present");
15128
- blockers.push(...collectSubmoduleBlockers(status, "post"));
15129
- return blockers;
15130
- }
15131
- function collectSubmoduleBlockers(status, phase) {
15132
- const submodules = Array.isArray(status.submodules) ? status.submodules : [];
15133
- const blockers = [];
15134
- for (const submodule of submodules) {
15135
- if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
15136
- if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
15137
- if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
15138
- }
15139
- return blockers;
15140
- }
15141
- function chooseBlockCode(status, blockers) {
15142
- if (blockers.includes("not_git_repo")) return "not_git_repo";
15143
- if (blockers.includes("branch_mismatch")) return "branch_mismatch";
15144
- if (blockers.includes("upstream_missing")) return "upstream_missing";
15145
- if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
15146
- if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
15147
- if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
15148
- if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
15149
- if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
15150
- return "preflight_blocked";
15151
- }
15152
- function codeToConvergenceStatus(code) {
15153
- if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
15154
- if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
15155
- return "blocked";
15156
- }
15157
- async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
15158
- if (!upstream) return { ok: false, error: "missing upstream" };
15159
- try {
15160
- await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
15161
- return { ok: true };
15162
- } catch (error) {
15163
- return { ok: false, error: formatGitError2(error) };
15164
- }
15165
- }
15166
- function block(base, code, blockingReasons) {
15167
- const normalizedReasons = normalizeBlockingReasons(blockingReasons);
15168
- return {
15169
- ...base,
15170
- success: false,
15171
- code,
15172
- allowed: false,
15173
- willRun: false,
15174
- executed: false,
15175
- blockingReasons: normalizedReasons
15176
- };
15177
- }
15178
- function normalizeBlockingReasons(reasons) {
15179
- const normalized = /* @__PURE__ */ new Set();
15180
- for (const reason of reasons) {
15181
- normalized.add(reason);
15182
- }
15183
- if ([
15184
- "conflicts_present",
15185
- "staged_changes_present",
15186
- "modified_changes_present",
15187
- "untracked_changes_present",
15188
- "deleted_changes_present",
15189
- "renamed_changes_present"
15190
- ].some((reason) => normalized.has(reason))) {
15191
- normalized.add("working_tree_not_clean");
15192
- }
15193
- return Array.from(normalized);
15194
- }
15195
- function buildConvergenceState(status, convergenceStatus) {
15196
- return {
15197
- status: convergenceStatus,
15198
- branch: status.branch,
15199
- headCommit: status.headCommit,
15200
- upstream: status.upstream,
15201
- ahead: status.ahead,
15202
- behind: status.behind,
15203
- dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
15204
- stashCount: status.stashCount,
15205
- submodules: summarizeSubmodules(status.submodules)
15206
- };
15207
- }
15208
- function summarizeSubmodules(submodules) {
15209
- return (submodules || []).map((submodule) => ({
15210
- path: submodule.path,
15211
- commit: submodule.commit,
15212
- dirty: submodule.dirty,
15213
- outOfSync: submodule.outOfSync,
15214
- ...submodule.error ? { error: submodule.error } : {}
15215
- }));
15216
- }
15217
- function normalizeOptionalString(value) {
15218
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
15219
- }
15220
- function formatGitError2(error) {
15221
- if (error instanceof GitCommandError) {
15222
- return error.stderr || error.stdout || error.message;
15223
- }
15224
- if (error instanceof Error) return error.message;
15225
- return String(error);
15226
- }
15227
- async function appendFastForwardLedger(result, outcome) {
15228
- if (!result.meshId) return;
15229
- try {
15230
- const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
15231
- appendLedgerEntry2(result.meshId, {
15232
- kind: "direct_fast_forward",
15233
- ...result.nodeId ? { nodeId: result.nodeId } : {},
15234
- payload: {
15235
- operation: "mesh_fast_forward_node",
15236
- outcome,
15237
- code: result.code,
15238
- workspace: result.workspace,
15239
- allowed: result.allowed,
15240
- dryRun: result.dryRun,
15241
- willRun: result.willRun,
15242
- executed: result.executed,
15243
- branch: result.postStatus?.branch ?? result.current?.branch,
15244
- upstream: result.postStatus?.upstream ?? result.current?.upstream,
15245
- before: result.current ? {
15246
- headCommit: result.current.headCommit,
15247
- ahead: result.current.ahead,
15248
- behind: result.current.behind
15249
- } : void 0,
15250
- after: result.postStatus ? {
15251
- headCommit: result.postStatus.headCommit,
15252
- ahead: result.postStatus.ahead,
15253
- behind: result.postStatus.behind
15254
- } : void 0,
15255
- blockingReasons: result.blockingReasons
15256
- }
15257
- });
15258
- } catch (error) {
15259
- result.ledgerError = error instanceof Error ? error.message : String(error);
15260
- }
15261
- }
15339
+ init_mesh_fast_forward();
15262
15340
 
15263
15341
  // src/mesh/mesh-ledger-reconciliation.ts
15264
15342
  function lastTimestamp(slice) {
@@ -34719,6 +34797,7 @@ function getAvailableIdeIds() {
34719
34797
  // src/commands/router.ts
34720
34798
  init_config();
34721
34799
  init_cli_detector();
34800
+ init_git_status();
34722
34801
  init_logger();
34723
34802
 
34724
34803
  // src/logging/command-log.ts
@@ -34871,6 +34950,7 @@ init_logger();
34871
34950
  init_mesh_coordinator();
34872
34951
  init_mesh_events();
34873
34952
  init_mesh_host_ownership();
34953
+ init_mesh_fast_forward();
34874
34954
 
34875
34955
  // src/mesh/preview-freshness.ts
34876
34956
  var import_node_child_process4 = require("child_process");
@@ -37525,8 +37605,8 @@ var DaemonCommandRouter = class {
37525
37605
  }
37526
37606
  }
37527
37607
  try {
37528
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37529
- const mesh = getMesh3(meshId);
37608
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37609
+ const mesh = getMesh2(meshId);
37530
37610
  if (mesh) return { mesh, inline: false, source: "local_config" };
37531
37611
  } catch {
37532
37612
  }
@@ -39451,8 +39531,8 @@ var DaemonCommandRouter = class {
39451
39531
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
39452
39532
  if (!meshId) return { success: false, error: "meshId required" };
39453
39533
  try {
39454
- const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39455
- const deleted = deleteMesh3(meshId);
39534
+ const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39535
+ const deleted = deleteMesh2(meshId);
39456
39536
  return { success: true, deleted };
39457
39537
  } catch (e) {
39458
39538
  return { success: false, error: e.message };
@@ -39570,7 +39650,7 @@ var DaemonCommandRouter = class {
39570
39650
  const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
39571
39651
  if (ownerFailure) return ownerFailure;
39572
39652
  try {
39573
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39653
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39574
39654
  const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
39575
39655
  const readOnly = args?.readOnly === true;
39576
39656
  const policy = {
@@ -39581,7 +39661,7 @@ var DaemonCommandRouter = class {
39581
39661
  const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
39582
39662
  const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
39583
39663
  const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
39584
- const node = addNode3(meshId, {
39664
+ const node = addNode2(meshId, {
39585
39665
  workspace,
39586
39666
  ...repoRoot ? { repoRoot } : {},
39587
39667
  ...daemonId ? { daemonId } : {},
@@ -39772,8 +39852,8 @@ var DaemonCommandRouter = class {
39772
39852
  if (meshRecord?.inline) {
39773
39853
  removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
39774
39854
  } else {
39775
- const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39776
- removed = removeNode3(meshId, nodeId);
39855
+ const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39856
+ removed = removeNode2(meshId, nodeId);
39777
39857
  if (removed) this.invalidateAggregateMeshStatus(meshId);
39778
39858
  }
39779
39859
  if (removed) {
@@ -39842,8 +39922,8 @@ var DaemonCommandRouter = class {
39842
39922
  };
39843
39923
  this.updateInlineMeshNode(meshId, mesh, node);
39844
39924
  } else {
39845
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39846
- node = addNode3(meshId, {
39925
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39926
+ node = addNode2(meshId, {
39847
39927
  workspace: result.worktreePath,
39848
39928
  repoRoot: result.worktreePath,
39849
39929
  daemonId: sourceNode.daemonId,
@@ -39998,8 +40078,8 @@ var DaemonCommandRouter = class {
39998
40078
  mesh = args.inlineMesh;
39999
40079
  this.inlineMeshCache.set(meshId, mesh);
40000
40080
  } else {
40001
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
40002
- mesh = getMesh3(meshId);
40081
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
40082
+ mesh = getMesh2(meshId);
40003
40083
  }
40004
40084
  if (!mesh) return { success: false, error: "Mesh not found" };
40005
40085
  const meshHost = resolveMeshHostStatus(mesh);
@@ -49371,6 +49451,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
49371
49451
  buildMeshHostRequiredFailure,
49372
49452
  buildMeshLedgerReconciliationEvidence,
49373
49453
  buildMeshLedgerReplicaEvidence,
49454
+ buildMeshNodeCapabilityTags,
49374
49455
  buildP2pRelayFailurePayload,
49375
49456
  buildPinnedGlobalInstallCommand,
49376
49457
  buildRuntimeSystemChatMessage,
@@ -49498,6 +49579,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
49498
49579
  maybeRunDaemonUpgradeHelperFromEnv,
49499
49580
  namedKeyToAnsi,
49500
49581
  namedKeysToAnsi,
49582
+ nodeSatisfiesRequiredTags,
49501
49583
  normalizeActiveChatData,
49502
49584
  normalizeChatMessage,
49503
49585
  normalizeChatMessageKind,
@@ -49509,6 +49591,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
49509
49591
  normalizeInteractivePrompt,
49510
49592
  normalizeInteractivePromptResponse,
49511
49593
  normalizeManagedStatus,
49594
+ normalizeMeshCapabilityTags,
49512
49595
  normalizeMeshDaemonRole,
49513
49596
  normalizeMeshTaskMode,
49514
49597
  normalizeMeshWorkerResult,
@@ -49567,7 +49650,6 @@ var V1_CONTRACT_VERSION = "1.0.0";
49567
49650
  startLocalIpcServer,
49568
49651
  suggestMeshRefineConfig,
49569
49652
  summarizeGitStatus,
49570
- syncMeshes,
49571
49653
  triggerMeshQueue,
49572
49654
  unregisterMeshCoordinator,
49573
49655
  updateConfig,