@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.mjs CHANGED
@@ -250,6 +250,263 @@ var init_git_executor = __esm({
250
250
  }
251
251
  });
252
252
 
253
+ // src/git/git-status.ts
254
+ async function getGitRepoStatus(workspace, options = {}) {
255
+ const lastCheckedAt = Date.now();
256
+ const includeSubmodules = options.includeSubmodules !== false;
257
+ try {
258
+ const repo = await resolveGitRepository(workspace, options);
259
+ let parsed = await readPorcelainStatus(repo, options);
260
+ let upstreamProbe = getInitialUpstreamProbe(parsed);
261
+ if (options.refreshUpstream) {
262
+ upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
263
+ if (upstreamProbe.upstreamStatus === "fresh") {
264
+ parsed = await readPorcelainStatus(repo, options);
265
+ }
266
+ }
267
+ const head = await readHead(repo, options);
268
+ const stashCount = await readStashCount(repo, options);
269
+ let submodules;
270
+ if (includeSubmodules) {
271
+ submodules = await getSubmoduleStatuses(repo, options);
272
+ }
273
+ return {
274
+ workspace: repo.workspace,
275
+ repoRoot: repo.repoRoot,
276
+ isGitRepo: true,
277
+ branch: parsed.branch,
278
+ headCommit: head.commit,
279
+ headMessage: head.message,
280
+ upstream: parsed.upstream,
281
+ upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
282
+ upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
283
+ upstreamFetchError: upstreamProbe.upstreamFetchError,
284
+ ahead: parsed.ahead,
285
+ behind: parsed.behind,
286
+ staged: parsed.staged,
287
+ modified: parsed.modified,
288
+ untracked: parsed.untracked,
289
+ deleted: parsed.deleted,
290
+ renamed: parsed.renamed,
291
+ hasConflicts: parsed.conflictFiles.length > 0,
292
+ conflictFiles: parsed.conflictFiles,
293
+ stashCount,
294
+ lastCheckedAt,
295
+ submodules
296
+ };
297
+ } catch (error) {
298
+ if (error instanceof GitCommandError) {
299
+ return emptyStatus(workspace, lastCheckedAt, error);
300
+ }
301
+ return emptyStatus(
302
+ workspace,
303
+ lastCheckedAt,
304
+ new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
305
+ );
306
+ }
307
+ }
308
+ async function readPorcelainStatus(repo, options) {
309
+ const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
310
+ return parsePorcelainV2Status(statusOutput.stdout);
311
+ }
312
+ function getInitialUpstreamProbe(parsed) {
313
+ return {
314
+ upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
315
+ };
316
+ }
317
+ async function refreshTrackedUpstream(repo, parsed, options) {
318
+ if (!parsed.upstream || !parsed.branch) {
319
+ return { upstreamStatus: "no_upstream" };
320
+ }
321
+ const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
322
+ if (!remoteName) {
323
+ return {
324
+ upstreamStatus: "stale",
325
+ upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
326
+ };
327
+ }
328
+ try {
329
+ await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
330
+ return {
331
+ upstreamStatus: "fresh",
332
+ upstreamFetchedAt: Date.now()
333
+ };
334
+ } catch (error) {
335
+ return {
336
+ upstreamStatus: "stale",
337
+ upstreamFetchError: formatGitError(error)
338
+ };
339
+ }
340
+ }
341
+ async function readBranchRemote(repo, branch, options) {
342
+ try {
343
+ const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
344
+ return result.stdout.trim() || null;
345
+ } catch {
346
+ return null;
347
+ }
348
+ }
349
+ function inferRemoteName(upstream) {
350
+ const [remoteName] = upstream.split("/");
351
+ return remoteName?.trim() || null;
352
+ }
353
+ function formatGitError(error) {
354
+ if (error instanceof GitCommandError) {
355
+ return error.stderr || error.message;
356
+ }
357
+ if (error instanceof Error) {
358
+ return error.message;
359
+ }
360
+ return String(error);
361
+ }
362
+ function parsePorcelainV2Status(output) {
363
+ const parsed = {
364
+ branch: null,
365
+ upstream: null,
366
+ ahead: 0,
367
+ behind: 0,
368
+ staged: 0,
369
+ modified: 0,
370
+ untracked: 0,
371
+ deleted: 0,
372
+ renamed: 0,
373
+ conflictFiles: []
374
+ };
375
+ for (const line of output.split("\n")) {
376
+ if (!line) continue;
377
+ if (line.startsWith("# branch.head ")) {
378
+ const branch = line.slice("# branch.head ".length).trim();
379
+ parsed.branch = branch && branch !== "(detached)" ? branch : null;
380
+ continue;
381
+ }
382
+ if (line.startsWith("# branch.upstream ")) {
383
+ parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
384
+ continue;
385
+ }
386
+ if (line.startsWith("# branch.ab ")) {
387
+ const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
388
+ if (match) {
389
+ parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
390
+ parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
391
+ }
392
+ continue;
393
+ }
394
+ if (line.startsWith("? ")) {
395
+ parsed.untracked += 1;
396
+ continue;
397
+ }
398
+ if (line.startsWith("u ")) {
399
+ const fields = line.split(" ");
400
+ const filePath = fields.slice(10).join(" ");
401
+ if (filePath) parsed.conflictFiles.push(filePath);
402
+ continue;
403
+ }
404
+ if (line.startsWith("1 ") || line.startsWith("2 ")) {
405
+ const fields = line.split(" ");
406
+ const xy = fields[1] ?? "..";
407
+ const indexStatus = xy[0] ?? ".";
408
+ const worktreeStatus = xy[1] ?? ".";
409
+ if (isStagedStatus(indexStatus)) parsed.staged += 1;
410
+ if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
411
+ if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
412
+ if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
413
+ if (xy.includes("U")) {
414
+ const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
415
+ if (filePath) parsed.conflictFiles.push(filePath);
416
+ }
417
+ }
418
+ }
419
+ parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
420
+ return parsed;
421
+ }
422
+ async function readHead(repo, options) {
423
+ try {
424
+ const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
425
+ const text = result.stdout.trimEnd();
426
+ if (!text) return { commit: null, message: null };
427
+ const [commit, ...messageParts] = text.split("\0");
428
+ return {
429
+ commit: commit || null,
430
+ message: messageParts.join("\0") || null
431
+ };
432
+ } catch {
433
+ return { commit: null, message: null };
434
+ }
435
+ }
436
+ async function readStashCount(repo, options) {
437
+ try {
438
+ const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
439
+ return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
440
+ } catch {
441
+ return 0;
442
+ }
443
+ }
444
+ function isStagedStatus(status) {
445
+ return status !== "." && status !== "?" && status !== "U";
446
+ }
447
+ function emptyStatus(workspace, lastCheckedAt, error) {
448
+ return {
449
+ workspace,
450
+ repoRoot: null,
451
+ isGitRepo: false,
452
+ branch: null,
453
+ headCommit: null,
454
+ headMessage: null,
455
+ upstream: null,
456
+ upstreamStatus: "unavailable",
457
+ ahead: 0,
458
+ behind: 0,
459
+ staged: 0,
460
+ modified: 0,
461
+ untracked: 0,
462
+ deleted: 0,
463
+ renamed: 0,
464
+ hasConflicts: false,
465
+ conflictFiles: [],
466
+ stashCount: 0,
467
+ lastCheckedAt,
468
+ error: error.stderr || error.message,
469
+ reason: error.reason
470
+ };
471
+ }
472
+ async function getSubmoduleStatuses(repo, options) {
473
+ if (!repo.repoRoot) return [];
474
+ try {
475
+ const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
476
+ return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
477
+ } catch {
478
+ return [];
479
+ }
480
+ }
481
+ function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
482
+ const submodules = [];
483
+ const ignoreSet = new Set(ignorePaths || []);
484
+ for (const line of output.split("\n")) {
485
+ if (!line.trim()) continue;
486
+ const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
487
+ if (!match) continue;
488
+ const prefix = match[1];
489
+ const commit = match[2];
490
+ const path40 = match[3];
491
+ if (ignoreSet.has(path40)) continue;
492
+ submodules.push({
493
+ path: path40,
494
+ commit,
495
+ repoPath: repoRoot + "/" + path40,
496
+ dirty: prefix === "+",
497
+ outOfSync: prefix === "-",
498
+ lastCheckedAt: Date.now()
499
+ });
500
+ }
501
+ return submodules;
502
+ }
503
+ var init_git_status = __esm({
504
+ "src/git/git-status.ts"() {
505
+ "use strict";
506
+ init_git_executor();
507
+ }
508
+ });
509
+
253
510
  // src/git/git-worktree.ts
254
511
  var git_worktree_exports = {};
255
512
  __export(git_worktree_exports, {
@@ -778,6 +1035,16 @@ function loadMeshConfig() {
778
1035
  return { meshes: [] };
779
1036
  }
780
1037
  }
1038
+ function normalizeCapabilityTags(value) {
1039
+ if (!Array.isArray(value)) return void 0;
1040
+ const seen = /* @__PURE__ */ new Set();
1041
+ const tags = value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
1042
+ if (seen.has(tag)) return false;
1043
+ seen.add(tag);
1044
+ return true;
1045
+ });
1046
+ return tags.length ? tags : void 0;
1047
+ }
781
1048
  function saveMeshConfig(config) {
782
1049
  const path40 = getMeshConfigPath();
783
1050
  writeFileSync2(path40, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
@@ -1063,6 +1330,7 @@ function addNode(meshId, opts) {
1063
1330
  repoRoot: opts.repoRoot,
1064
1331
  daemonId: opts.daemonId,
1065
1332
  machineId: opts.machineId,
1333
+ capabilities: normalizeCapabilityTags(opts.capabilities),
1066
1334
  userOverrides: opts.userOverrides || {},
1067
1335
  policy: opts.policy || {},
1068
1336
  isLocalWorktree: opts.isLocalWorktree,
@@ -2545,6 +2813,369 @@ var init_mesh_ledger = __esm({
2545
2813
  }
2546
2814
  });
2547
2815
 
2816
+ // src/mesh/mesh-fast-forward.ts
2817
+ async function fastForwardMeshNode(args) {
2818
+ const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
2819
+ const nodeId = normalizeOptionalString(args.nodeId);
2820
+ const meshId = normalizeOptionalString(args.meshId);
2821
+ const requestedBranch = normalizeOptionalString(args.branch);
2822
+ const trigger = normalizeOptionalString(args.trigger) || "manual";
2823
+ const updateSubmodules = args.updateSubmodules === true;
2824
+ const dryRun = args.dryRun === true || args.execute !== true;
2825
+ const plannedSteps = buildPlannedSteps(updateSubmodules);
2826
+ const base = {
2827
+ ...nodeId ? { nodeId } : {},
2828
+ ...meshId ? { meshId } : {},
2829
+ workspace,
2830
+ dryRun,
2831
+ updateSubmodules,
2832
+ plannedSteps,
2833
+ trigger
2834
+ };
2835
+ if (!workspace) {
2836
+ return block(base, "invalid_workspace", ["workspace_required"]);
2837
+ }
2838
+ const current = await getGitRepoStatus(workspace, {
2839
+ ...STATUS_OPTIONS,
2840
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
2841
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
2842
+ });
2843
+ const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
2844
+ if (earlyBlockers.length > 0) {
2845
+ const result2 = {
2846
+ ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
2847
+ current,
2848
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
2849
+ };
2850
+ await appendFastForwardLedger(result2, "blocked");
2851
+ return result2;
2852
+ }
2853
+ if (current.behind === 0) {
2854
+ const result2 = {
2855
+ ...base,
2856
+ success: true,
2857
+ code: "already_up_to_date",
2858
+ allowed: true,
2859
+ willRun: false,
2860
+ executed: false,
2861
+ blockingReasons: [],
2862
+ current,
2863
+ preStatus: current,
2864
+ postStatus: current,
2865
+ finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
2866
+ };
2867
+ await appendFastForwardLedger(result2, "noop");
2868
+ return result2;
2869
+ }
2870
+ const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
2871
+ if (!ancestorCheck.ok) {
2872
+ const result2 = {
2873
+ ...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
2874
+ current,
2875
+ preStatus: current,
2876
+ operationError: ancestorCheck.error,
2877
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
2878
+ };
2879
+ await appendFastForwardLedger(result2, "blocked");
2880
+ return result2;
2881
+ }
2882
+ if (dryRun) {
2883
+ const result2 = {
2884
+ ...base,
2885
+ success: true,
2886
+ code: "fast_forward_available",
2887
+ allowed: true,
2888
+ willRun: false,
2889
+ executed: false,
2890
+ blockingReasons: [],
2891
+ current,
2892
+ preStatus: current,
2893
+ finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
2894
+ };
2895
+ await appendFastForwardLedger(result2, "dry_run");
2896
+ return result2;
2897
+ }
2898
+ try {
2899
+ await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
2900
+ } catch (error) {
2901
+ const result2 = {
2902
+ ...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
2903
+ current,
2904
+ preStatus: current,
2905
+ operationError: formatGitError2(error),
2906
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
2907
+ };
2908
+ await appendFastForwardLedger(result2, "failed");
2909
+ return result2;
2910
+ }
2911
+ let postStatus = await getGitRepoStatus(workspace, {
2912
+ ...STATUS_OPTIONS,
2913
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
2914
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
2915
+ });
2916
+ const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
2917
+ let submoduleFollowUpRequired = false;
2918
+ let operationError;
2919
+ if (submoduleIssues.length > 0) {
2920
+ if (updateSubmodules) {
2921
+ try {
2922
+ await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
2923
+ postStatus = await getGitRepoStatus(workspace, {
2924
+ ...STATUS_OPTIONS,
2925
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
2926
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
2927
+ });
2928
+ } catch (error) {
2929
+ operationError = formatGitError2(error);
2930
+ }
2931
+ } else {
2932
+ submoduleFollowUpRequired = true;
2933
+ }
2934
+ }
2935
+ const postBlockers = collectPostExecutionBlockers(postStatus);
2936
+ if (operationError) postBlockers.push("submodule_update_failed");
2937
+ if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
2938
+ const success = postBlockers.length === 0 || submoduleFollowUpRequired;
2939
+ const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
2940
+ const result = {
2941
+ ...base,
2942
+ success,
2943
+ code,
2944
+ allowed: true,
2945
+ willRun: true,
2946
+ executed: true,
2947
+ blockingReasons: postBlockers,
2948
+ current,
2949
+ preStatus: current,
2950
+ postStatus,
2951
+ ...operationError ? { operationError } : {},
2952
+ finalBranchConvergenceState: buildConvergenceState(
2953
+ postStatus,
2954
+ postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
2955
+ )
2956
+ };
2957
+ await appendFastForwardLedger(result, success ? "executed" : "failed");
2958
+ return result;
2959
+ }
2960
+ function buildPlannedSteps(updateSubmodules) {
2961
+ const steps = [
2962
+ {
2963
+ operation: "refresh_upstream",
2964
+ description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
2965
+ safe: true,
2966
+ willMutateWorktree: false
2967
+ },
2968
+ {
2969
+ operation: "verify_clean_worktree",
2970
+ description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
2971
+ safe: true,
2972
+ willMutateWorktree: false
2973
+ },
2974
+ {
2975
+ operation: "verify_fast_forward",
2976
+ description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
2977
+ safe: true,
2978
+ willMutateWorktree: false
2979
+ },
2980
+ {
2981
+ operation: "merge_ff_only",
2982
+ description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
2983
+ safe: true,
2984
+ willMutateWorktree: true
2985
+ }
2986
+ ];
2987
+ if (updateSubmodules) {
2988
+ steps.push({
2989
+ operation: "submodule_update",
2990
+ description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
2991
+ safe: true,
2992
+ willMutateWorktree: true
2993
+ });
2994
+ }
2995
+ steps.push({
2996
+ operation: "verify_post_status",
2997
+ description: "Re-read daemon-owned git status and report final branch convergence state.",
2998
+ safe: true,
2999
+ willMutateWorktree: false
3000
+ });
3001
+ return steps;
3002
+ }
3003
+ function collectPreflightBlockers(status, requestedBranch) {
3004
+ const blockers = [];
3005
+ if (!status.isGitRepo) blockers.push("not_git_repo");
3006
+ if (!status.branch) blockers.push("detached_head_or_unknown_branch");
3007
+ if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
3008
+ if (!status.upstream) blockers.push("upstream_missing");
3009
+ if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
3010
+ if (status.hasConflicts) blockers.push("conflicts_present");
3011
+ if (status.staged > 0) blockers.push("staged_changes_present");
3012
+ if (status.modified > 0) blockers.push("modified_changes_present");
3013
+ if (status.untracked > 0) blockers.push("untracked_changes_present");
3014
+ if (status.deleted > 0) blockers.push("deleted_changes_present");
3015
+ if (status.renamed > 0) blockers.push("renamed_changes_present");
3016
+ if (status.stashCount > 0) blockers.push("stash_entries_present");
3017
+ blockers.push(...collectSubmoduleBlockers(status, "pre"));
3018
+ if (status.ahead > 0 && status.behind > 0) {
3019
+ blockers.push("branch_diverged_from_upstream");
3020
+ blockers.push("branch_has_local_commits");
3021
+ } else if (status.ahead > 0) blockers.push("branch_has_local_commits");
3022
+ return blockers;
3023
+ }
3024
+ function collectPostExecutionBlockers(status) {
3025
+ const blockers = [];
3026
+ if (!status.isGitRepo) blockers.push("post_not_git_repo");
3027
+ if (status.hasConflicts) blockers.push("post_conflicts_present");
3028
+ if (status.ahead !== 0) blockers.push("post_branch_ahead");
3029
+ if (status.behind !== 0) blockers.push("post_branch_still_behind");
3030
+ if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
3031
+ blockers.push("post_working_tree_not_clean");
3032
+ }
3033
+ if (status.stashCount > 0) blockers.push("post_stash_entries_present");
3034
+ blockers.push(...collectSubmoduleBlockers(status, "post"));
3035
+ return blockers;
3036
+ }
3037
+ function collectSubmoduleBlockers(status, phase) {
3038
+ const submodules = Array.isArray(status.submodules) ? status.submodules : [];
3039
+ const blockers = [];
3040
+ for (const submodule of submodules) {
3041
+ if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
3042
+ if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
3043
+ if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
3044
+ }
3045
+ return blockers;
3046
+ }
3047
+ function chooseBlockCode(status, blockers) {
3048
+ if (blockers.includes("not_git_repo")) return "not_git_repo";
3049
+ if (blockers.includes("branch_mismatch")) return "branch_mismatch";
3050
+ if (blockers.includes("upstream_missing")) return "upstream_missing";
3051
+ if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
3052
+ if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
3053
+ if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
3054
+ if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
3055
+ if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
3056
+ return "preflight_blocked";
3057
+ }
3058
+ function codeToConvergenceStatus(code) {
3059
+ if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
3060
+ if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
3061
+ return "blocked";
3062
+ }
3063
+ async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
3064
+ if (!upstream) return { ok: false, error: "missing upstream" };
3065
+ try {
3066
+ await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
3067
+ return { ok: true };
3068
+ } catch (error) {
3069
+ return { ok: false, error: formatGitError2(error) };
3070
+ }
3071
+ }
3072
+ function block(base, code, blockingReasons) {
3073
+ const normalizedReasons = normalizeBlockingReasons(blockingReasons);
3074
+ return {
3075
+ ...base,
3076
+ success: false,
3077
+ code,
3078
+ allowed: false,
3079
+ willRun: false,
3080
+ executed: false,
3081
+ blockingReasons: normalizedReasons
3082
+ };
3083
+ }
3084
+ function normalizeBlockingReasons(reasons) {
3085
+ const normalized = /* @__PURE__ */ new Set();
3086
+ for (const reason of reasons) {
3087
+ normalized.add(reason);
3088
+ }
3089
+ if ([
3090
+ "conflicts_present",
3091
+ "staged_changes_present",
3092
+ "modified_changes_present",
3093
+ "untracked_changes_present",
3094
+ "deleted_changes_present",
3095
+ "renamed_changes_present"
3096
+ ].some((reason) => normalized.has(reason))) {
3097
+ normalized.add("working_tree_not_clean");
3098
+ }
3099
+ return Array.from(normalized);
3100
+ }
3101
+ function buildConvergenceState(status, convergenceStatus) {
3102
+ return {
3103
+ status: convergenceStatus,
3104
+ branch: status.branch,
3105
+ headCommit: status.headCommit,
3106
+ upstream: status.upstream,
3107
+ ahead: status.ahead,
3108
+ behind: status.behind,
3109
+ dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
3110
+ stashCount: status.stashCount,
3111
+ submodules: summarizeSubmodules(status.submodules)
3112
+ };
3113
+ }
3114
+ function summarizeSubmodules(submodules) {
3115
+ return (submodules || []).map((submodule) => ({
3116
+ path: submodule.path,
3117
+ commit: submodule.commit,
3118
+ dirty: submodule.dirty,
3119
+ outOfSync: submodule.outOfSync,
3120
+ ...submodule.error ? { error: submodule.error } : {}
3121
+ }));
3122
+ }
3123
+ function normalizeOptionalString(value) {
3124
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
3125
+ }
3126
+ function formatGitError2(error) {
3127
+ if (error instanceof GitCommandError) {
3128
+ return error.stderr || error.stdout || error.message;
3129
+ }
3130
+ if (error instanceof Error) return error.message;
3131
+ return String(error);
3132
+ }
3133
+ async function appendFastForwardLedger(result, outcome) {
3134
+ if (!result.meshId) return;
3135
+ try {
3136
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
3137
+ appendLedgerEntry2(result.meshId, {
3138
+ kind: "direct_fast_forward",
3139
+ ...result.nodeId ? { nodeId: result.nodeId } : {},
3140
+ payload: {
3141
+ operation: "mesh_fast_forward_node",
3142
+ trigger: result.trigger || "manual",
3143
+ outcome,
3144
+ code: result.code,
3145
+ workspace: result.workspace,
3146
+ allowed: result.allowed,
3147
+ dryRun: result.dryRun,
3148
+ willRun: result.willRun,
3149
+ executed: result.executed,
3150
+ branch: result.postStatus?.branch ?? result.current?.branch,
3151
+ upstream: result.postStatus?.upstream ?? result.current?.upstream,
3152
+ before: result.current ? {
3153
+ headCommit: result.current.headCommit,
3154
+ ahead: result.current.ahead,
3155
+ behind: result.current.behind
3156
+ } : void 0,
3157
+ after: result.postStatus ? {
3158
+ headCommit: result.postStatus.headCommit,
3159
+ ahead: result.postStatus.ahead,
3160
+ behind: result.postStatus.behind
3161
+ } : void 0,
3162
+ blockingReasons: result.blockingReasons
3163
+ }
3164
+ });
3165
+ } catch (error) {
3166
+ result.ledgerError = error instanceof Error ? error.message : String(error);
3167
+ }
3168
+ }
3169
+ var STATUS_OPTIONS;
3170
+ var init_mesh_fast_forward = __esm({
3171
+ "src/mesh/mesh-fast-forward.ts"() {
3172
+ "use strict";
3173
+ init_git_status();
3174
+ init_git_executor();
3175
+ STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
3176
+ }
3177
+ });
3178
+
2548
3179
  // src/mesh/beads-db.ts
2549
3180
  import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, statSync as statSync4 } from "fs";
2550
3181
  import { dirname as dirname2, join as join12 } from "path";
@@ -2566,6 +3197,7 @@ var init_beads_db = __esm({
2566
3197
  "src/mesh/beads-db.ts"() {
2567
3198
  "use strict";
2568
3199
  init_mesh_ledger();
3200
+ init_mesh_work_queue();
2569
3201
  BeadsDB = class _BeadsDB {
2570
3202
  static instance;
2571
3203
  db;
@@ -2786,25 +3418,29 @@ var init_beads_db = __esm({
2786
3418
  return row !== void 0;
2787
3419
  }
2788
3420
  // O(1) claim: transaction ensures only one session claims a pending task
2789
- claimNextQueueTask(meshId, nodeId, sessionId) {
3421
+ claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags = []) {
2790
3422
  return this.transaction(() => {
2791
3423
  this.ensureLegacyQueueMigrated(meshId);
2792
3424
  if (this.hasActiveAssignment(meshId, sessionId, nodeId)) return null;
2793
- const row = this.db.prepare(`
3425
+ const rows = [
3426
+ ...this.db.prepare(`
2794
3427
  SELECT payload FROM mesh_queue
2795
3428
  WHERE mesh_id = ? AND status = 'pending' AND target_session_id = ?
2796
- ORDER BY created_at ASC LIMIT 1
2797
- `).get(meshId, sessionId) || this.db.prepare(`
3429
+ ORDER BY created_at ASC
3430
+ `).all(meshId, sessionId),
3431
+ ...this.db.prepare(`
2798
3432
  SELECT payload FROM mesh_queue
2799
3433
  WHERE mesh_id = ? AND status = 'pending' AND target_node_id = ? AND target_session_id IS NULL
2800
- ORDER BY created_at ASC LIMIT 1
2801
- `).get(meshId, nodeId) || this.db.prepare(`
3434
+ ORDER BY created_at ASC
3435
+ `).all(meshId, nodeId),
3436
+ ...this.db.prepare(`
2802
3437
  SELECT payload FROM mesh_queue
2803
3438
  WHERE mesh_id = ? AND status = 'pending' AND target_node_id IS NULL AND target_session_id IS NULL
2804
- ORDER BY created_at ASC LIMIT 1
2805
- `).get(meshId);
2806
- if (!row) return null;
2807
- const entry = JSON.parse(row.payload);
3439
+ ORDER BY created_at ASC
3440
+ `).all(meshId)
3441
+ ];
3442
+ const entry = rows.map((row) => JSON.parse(row.payload)).find((candidate) => nodeSatisfiesRequiredTags(candidate.requiredTags, capabilityTags));
3443
+ if (!entry) return null;
2808
3444
  const now = (/* @__PURE__ */ new Date()).toISOString();
2809
3445
  entry.status = "assigned";
2810
3446
  entry.assignedNodeId = nodeId;
@@ -2952,6 +3588,7 @@ __export(mesh_work_queue_exports, {
2952
3588
  __clearMeshQueueForTests: () => __clearMeshQueueForTests,
2953
3589
  __replaceMeshQueueForTests: () => __replaceMeshQueueForTests,
2954
3590
  __resetBeadsDBForTests: () => __resetBeadsDBForTests,
3591
+ buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
2955
3592
  cancelTask: () => cancelTask,
2956
3593
  claimNextTask: () => claimNextTask,
2957
3594
  cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
@@ -2962,6 +3599,8 @@ __export(mesh_work_queue_exports, {
2962
3599
  getQueue: () => getQueue,
2963
3600
  insertDirectDispatch: () => insertDirectDispatch,
2964
3601
  markStaleDirectDispatches: () => markStaleDirectDispatches,
3602
+ nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
3603
+ normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
2965
3604
  normalizeMeshTaskMode: () => normalizeMeshTaskMode,
2966
3605
  recordTaskAutoLaunch: () => recordTaskAutoLaunch,
2967
3606
  requeueTask: () => requeueTask,
@@ -2997,6 +3636,35 @@ function validateMeshTaskModeRequest(mode, message) {
2997
3636
  ]
2998
3637
  };
2999
3638
  }
3639
+ function normalizeMeshCapabilityTags(value) {
3640
+ if (!Array.isArray(value)) return [];
3641
+ const seen = /* @__PURE__ */ new Set();
3642
+ return value.map((tag) => typeof tag === "string" ? tag.trim() : "").filter(Boolean).filter((tag) => {
3643
+ if (seen.has(tag)) return false;
3644
+ seen.add(tag);
3645
+ return true;
3646
+ });
3647
+ }
3648
+ function firstProviderPriority(policy) {
3649
+ const raw = policy && typeof policy === "object" && !Array.isArray(policy) ? policy.providerPriority : void 0;
3650
+ if (!Array.isArray(raw)) return void 0;
3651
+ return raw.find((type) => typeof type === "string" && type.trim())?.trim();
3652
+ }
3653
+ function buildMeshNodeCapabilityTags(node, providerType) {
3654
+ const provider = typeof providerType === "string" && providerType.trim() ? providerType.trim() : firstProviderPriority(node?.policy);
3655
+ return normalizeMeshCapabilityTags([
3656
+ ...Array.isArray(node?.capabilities) ? node.capabilities : [],
3657
+ `os=${process.platform}`,
3658
+ `arch=${process.arch}`,
3659
+ ...provider ? [`provider=${provider}`] : []
3660
+ ]);
3661
+ }
3662
+ function nodeSatisfiesRequiredTags(requiredTags, capabilityTags) {
3663
+ const required = normalizeMeshCapabilityTags(requiredTags);
3664
+ if (required.length === 0) return true;
3665
+ const available = new Set(normalizeMeshCapabilityTags(capabilityTags));
3666
+ return required.every((tag) => available.has(tag));
3667
+ }
3000
3668
  function withQueueLock(_meshId, fn) {
3001
3669
  return BeadsDB.getInstance().transaction(fn);
3002
3670
  }
@@ -3014,6 +3682,7 @@ function enqueueTask(meshId, message, opts) {
3014
3682
  taskMode: modeValidation.taskMode,
3015
3683
  targetNodeId: opts?.targetNodeId,
3016
3684
  targetSessionId: opts?.targetSessionId,
3685
+ requiredTags: normalizeMeshCapabilityTags(opts?.requiredTags),
3017
3686
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3018
3687
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3019
3688
  };
@@ -3026,8 +3695,8 @@ function getQueue(meshId, opts) {
3026
3695
  function getMeshQueueRevision(meshId) {
3027
3696
  return BeadsDB.getInstance().getQueueRevision(meshId);
3028
3697
  }
3029
- function claimNextTask(meshId, nodeId, sessionId) {
3030
- return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId);
3698
+ function claimNextTask(meshId, nodeId, sessionId, capabilityTags) {
3699
+ return BeadsDB.getInstance().claimNextQueueTask(meshId, nodeId, sessionId, capabilityTags);
3031
3700
  }
3032
3701
  function updateTaskStatus(meshId, taskId, status, opts) {
3033
3702
  requireMeshHostQueueOwner(opts);
@@ -3318,6 +3987,7 @@ var init_cli_detector = __esm({
3318
3987
  // src/mesh/mesh-events.ts
3319
3988
  var mesh_events_exports = {};
3320
3989
  __export(mesh_events_exports, {
3990
+ __resetIdleAutoFastForwardForTests: () => __resetIdleAutoFastForwardForTests,
3321
3991
  clearPendingMeshCoordinatorEvents: () => clearPendingMeshCoordinatorEvents,
3322
3992
  drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
3323
3993
  getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
@@ -3340,6 +4010,9 @@ function getCachedMeshByWorkspace(workspace) {
3340
4010
  function readWorkerResultMetadata(event) {
3341
4011
  return readRecord2(event.workerResult) || readRecord2(event.meshWorkerResult) || readRecord2(event.structuredResult);
3342
4012
  }
4013
+ function __resetIdleAutoFastForwardForTests() {
4014
+ idleAutoFastForwardLastAttempt.clear();
4015
+ }
3343
4016
  function sweepExpiredRemoteIdleSessions() {
3344
4017
  const now = Date.now();
3345
4018
  for (const [key, session] of remoteIdleSessions) {
@@ -3765,13 +4438,14 @@ function buildLongGeneratingCompletionReconciliation(args) {
3765
4438
  };
3766
4439
  }
3767
4440
  function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
3768
- const task = claimNextTask(meshId, nodeId, sessionId);
4441
+ const mesh = getMeshWithCache(components, meshId);
4442
+ const node = mesh?.nodes.find((n) => n.id === nodeId);
4443
+ const capabilityTags = buildMeshNodeCapabilityTags(node, providerType);
4444
+ const task = claimNextTask(meshId, nodeId, sessionId, capabilityTags);
3769
4445
  if (!task) {
3770
4446
  return false;
3771
4447
  }
3772
4448
  LOG.info("MeshQueue", `Node ${nodeId} (${sessionId}) pulled task ${task.id}`);
3773
- const mesh = getMeshWithCache(components, meshId);
3774
- const node = mesh?.nodes.find((n) => n.id === nodeId);
3775
4449
  if (node?.daemonId && components.dispatchMeshCommand) {
3776
4450
  const isLocalNode = components.cliManager.adapters.has(sessionId);
3777
4451
  if (!isLocalNode) {
@@ -4077,6 +4751,55 @@ async function triggerMeshQueue(components, meshId) {
4077
4751
  }
4078
4752
  await maybeAutoLaunchOneQueueSession(components, meshId, mesh);
4079
4753
  }
4754
+ async function maybeAutoFastForwardIdleNode(components, args) {
4755
+ const mesh = getMeshWithCache(components, args.meshId);
4756
+ const node = mesh?.nodes?.find((candidate) => candidate?.id === args.nodeId || candidate?.nodeId === args.nodeId);
4757
+ const workspace = readNonEmptyString2(node?.workspace);
4758
+ if (!workspace) return;
4759
+ if (!existsSync13(workspace)) return;
4760
+ const throttleKey = `${args.meshId}:${args.nodeId}`;
4761
+ const now = Date.now();
4762
+ const lastAttempt = idleAutoFastForwardLastAttempt.get(throttleKey) || 0;
4763
+ if (now - lastAttempt < IDLE_AUTO_FAST_FORWARD_THROTTLE_MS) return;
4764
+ idleAutoFastForwardLastAttempt.set(throttleKey, now);
4765
+ const submoduleIgnorePaths = Array.isArray(node?.policy?.submoduleIgnorePaths) ? node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
4766
+ try {
4767
+ const dryRun = await fastForwardMeshNode({
4768
+ meshId: args.meshId,
4769
+ nodeId: args.nodeId,
4770
+ workspace,
4771
+ execute: false,
4772
+ dryRun: true,
4773
+ updateSubmodules: false,
4774
+ submoduleIgnorePaths,
4775
+ trigger: "idle_auto"
4776
+ });
4777
+ if (!dryRun || dryRun.code !== "fast_forward_available" || dryRun.allowed !== true) return;
4778
+ await fastForwardMeshNode({
4779
+ meshId: args.meshId,
4780
+ nodeId: args.nodeId,
4781
+ workspace,
4782
+ execute: true,
4783
+ dryRun: false,
4784
+ updateSubmodules: false,
4785
+ submoduleIgnorePaths,
4786
+ trigger: "idle_auto"
4787
+ });
4788
+ } catch (e) {
4789
+ LOG.warn("MeshFastForward", `Idle auto fast-forward check failed for ${args.nodeId}: ${e?.message || e}`);
4790
+ }
4791
+ }
4792
+ function runIdleMaintenanceThenAssignQueue(components, args) {
4793
+ setImmediate(() => {
4794
+ maybeAutoFastForwardIdleNode(components, args).finally(() => {
4795
+ try {
4796
+ tryAssignQueueTask(components, args.meshId, args.nodeId, args.sessionId, args.providerType);
4797
+ } catch (e) {
4798
+ LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${args.nodeId}: ${e?.message || e}`);
4799
+ }
4800
+ });
4801
+ });
4802
+ }
4080
4803
  function buildMeshSystemMessage(args) {
4081
4804
  const metadata = formatCompletionMetadata(args.metadataEvent);
4082
4805
  if (args.event === "agent:generating_completed") {
@@ -4298,9 +5021,7 @@ function injectMeshSystemMessage(components, args) {
4298
5021
  updateDirectDispatchStatus(args.meshId, sessionId, "completed");
4299
5022
  setImmediate(() => cleanupTerminalDirectDispatches());
4300
5023
  if (nodeId && providerType) {
4301
- setImmediate(() => {
4302
- tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
4303
- });
5024
+ runIdleMaintenanceThenAssignQueue(components, { meshId: args.meshId, nodeId, sessionId, providerType });
4304
5025
  }
4305
5026
  }
4306
5027
  } else if (args.event === "agent:ready") {
@@ -4354,8 +5075,14 @@ function injectMeshSystemMessage(components, args) {
4354
5075
  expiresAt: Date.now() + REMOTE_IDLE_SESSION_TTL_MS
4355
5076
  });
4356
5077
  setImmediate(() => {
4357
- const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
4358
- if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
5078
+ maybeAutoFastForwardIdleNode(components, { meshId: args.meshId, nodeId, sessionId, providerType }).finally(() => {
5079
+ try {
5080
+ const assigned = tryAssignQueueTask(components, args.meshId, nodeId, sessionId, providerType);
5081
+ if (assigned) remoteIdleSessions.delete(`${nodeId}:${sessionId}`);
5082
+ } catch (e) {
5083
+ LOG.warn("MeshQueue", `Failed to assign idle queue task after maintenance for ${nodeId}: ${e?.message || e}`);
5084
+ }
5085
+ });
4359
5086
  });
4360
5087
  }
4361
5088
  } else if (args.event === "agent:generating_started") {
@@ -4631,7 +5358,7 @@ function setupMeshEventForwarding(components) {
4631
5358
  });
4632
5359
  });
4633
5360
  }
4634
- var 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;
5361
+ var 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;
4635
5362
  var init_mesh_events = __esm({
4636
5363
  "src/mesh/mesh-events.ts"() {
4637
5364
  "use strict";
@@ -4642,10 +5369,13 @@ var init_mesh_events = __esm({
4642
5369
  init_mesh_ledger();
4643
5370
  init_mesh_work_queue();
4644
5371
  init_beads_db();
5372
+ init_mesh_fast_forward();
4645
5373
  REMOTE_IDLE_SESSION_TTL_MS = 5 * 60 * 1e3;
4646
5374
  remoteIdleSessions = /* @__PURE__ */ new Map();
4647
5375
  meshByWorkspaceCache = /* @__PURE__ */ new Map();
4648
5376
  MESH_WORKSPACE_CACHE_TTL_MS = 5e3;
5377
+ IDLE_AUTO_FAST_FORWARD_THROTTLE_MS = 30 * 60 * 1e3;
5378
+ idleAutoFastForwardLastAttempt = /* @__PURE__ */ new Map();
4649
5379
  REFINE_TERMINAL_EVENTS = /* @__PURE__ */ new Set(["refine:completed", "refine:failed"]);
4650
5380
  MAX_PENDING_EVENTS_BYTES = 100 * 1024;
4651
5381
  MAX_PENDING_EVENTS_KEEP = 50;
@@ -12274,258 +13004,7 @@ init_repo_mesh_types();
12274
13004
 
12275
13005
  // src/git/index.ts
12276
13006
  init_git_executor();
12277
-
12278
- // src/git/git-status.ts
12279
- init_git_executor();
12280
- async function getGitRepoStatus(workspace, options = {}) {
12281
- const lastCheckedAt = Date.now();
12282
- const includeSubmodules = options.includeSubmodules !== false;
12283
- try {
12284
- const repo = await resolveGitRepository(workspace, options);
12285
- let parsed = await readPorcelainStatus(repo, options);
12286
- let upstreamProbe = getInitialUpstreamProbe(parsed);
12287
- if (options.refreshUpstream) {
12288
- upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
12289
- if (upstreamProbe.upstreamStatus === "fresh") {
12290
- parsed = await readPorcelainStatus(repo, options);
12291
- }
12292
- }
12293
- const head = await readHead(repo, options);
12294
- const stashCount = await readStashCount(repo, options);
12295
- let submodules;
12296
- if (includeSubmodules) {
12297
- submodules = await getSubmoduleStatuses(repo, options);
12298
- }
12299
- return {
12300
- workspace: repo.workspace,
12301
- repoRoot: repo.repoRoot,
12302
- isGitRepo: true,
12303
- branch: parsed.branch,
12304
- headCommit: head.commit,
12305
- headMessage: head.message,
12306
- upstream: parsed.upstream,
12307
- upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
12308
- upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
12309
- upstreamFetchError: upstreamProbe.upstreamFetchError,
12310
- ahead: parsed.ahead,
12311
- behind: parsed.behind,
12312
- staged: parsed.staged,
12313
- modified: parsed.modified,
12314
- untracked: parsed.untracked,
12315
- deleted: parsed.deleted,
12316
- renamed: parsed.renamed,
12317
- hasConflicts: parsed.conflictFiles.length > 0,
12318
- conflictFiles: parsed.conflictFiles,
12319
- stashCount,
12320
- lastCheckedAt,
12321
- submodules
12322
- };
12323
- } catch (error) {
12324
- if (error instanceof GitCommandError) {
12325
- return emptyStatus(workspace, lastCheckedAt, error);
12326
- }
12327
- return emptyStatus(
12328
- workspace,
12329
- lastCheckedAt,
12330
- new GitCommandError("git_command_failed", "Failed to read Git status", { cause: error })
12331
- );
12332
- }
12333
- }
12334
- async function readPorcelainStatus(repo, options) {
12335
- const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
12336
- return parsePorcelainV2Status(statusOutput.stdout);
12337
- }
12338
- function getInitialUpstreamProbe(parsed) {
12339
- return {
12340
- upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
12341
- };
12342
- }
12343
- async function refreshTrackedUpstream(repo, parsed, options) {
12344
- if (!parsed.upstream || !parsed.branch) {
12345
- return { upstreamStatus: "no_upstream" };
12346
- }
12347
- const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
12348
- if (!remoteName) {
12349
- return {
12350
- upstreamStatus: "stale",
12351
- upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
12352
- };
12353
- }
12354
- try {
12355
- await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
12356
- return {
12357
- upstreamStatus: "fresh",
12358
- upstreamFetchedAt: Date.now()
12359
- };
12360
- } catch (error) {
12361
- return {
12362
- upstreamStatus: "stale",
12363
- upstreamFetchError: formatGitError(error)
12364
- };
12365
- }
12366
- }
12367
- async function readBranchRemote(repo, branch, options) {
12368
- try {
12369
- const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
12370
- return result.stdout.trim() || null;
12371
- } catch {
12372
- return null;
12373
- }
12374
- }
12375
- function inferRemoteName(upstream) {
12376
- const [remoteName] = upstream.split("/");
12377
- return remoteName?.trim() || null;
12378
- }
12379
- function formatGitError(error) {
12380
- if (error instanceof GitCommandError) {
12381
- return error.stderr || error.message;
12382
- }
12383
- if (error instanceof Error) {
12384
- return error.message;
12385
- }
12386
- return String(error);
12387
- }
12388
- function parsePorcelainV2Status(output) {
12389
- const parsed = {
12390
- branch: null,
12391
- upstream: null,
12392
- ahead: 0,
12393
- behind: 0,
12394
- staged: 0,
12395
- modified: 0,
12396
- untracked: 0,
12397
- deleted: 0,
12398
- renamed: 0,
12399
- conflictFiles: []
12400
- };
12401
- for (const line of output.split("\n")) {
12402
- if (!line) continue;
12403
- if (line.startsWith("# branch.head ")) {
12404
- const branch = line.slice("# branch.head ".length).trim();
12405
- parsed.branch = branch && branch !== "(detached)" ? branch : null;
12406
- continue;
12407
- }
12408
- if (line.startsWith("# branch.upstream ")) {
12409
- parsed.upstream = line.slice("# branch.upstream ".length).trim() || null;
12410
- continue;
12411
- }
12412
- if (line.startsWith("# branch.ab ")) {
12413
- const match = line.match(/\+(-?\d+)\s+-(-?\d+)/);
12414
- if (match) {
12415
- parsed.ahead = Number.parseInt(match[1] ?? "0", 10) || 0;
12416
- parsed.behind = Number.parseInt(match[2] ?? "0", 10) || 0;
12417
- }
12418
- continue;
12419
- }
12420
- if (line.startsWith("? ")) {
12421
- parsed.untracked += 1;
12422
- continue;
12423
- }
12424
- if (line.startsWith("u ")) {
12425
- const fields = line.split(" ");
12426
- const filePath = fields.slice(10).join(" ");
12427
- if (filePath) parsed.conflictFiles.push(filePath);
12428
- continue;
12429
- }
12430
- if (line.startsWith("1 ") || line.startsWith("2 ")) {
12431
- const fields = line.split(" ");
12432
- const xy = fields[1] ?? "..";
12433
- const indexStatus = xy[0] ?? ".";
12434
- const worktreeStatus = xy[1] ?? ".";
12435
- if (isStagedStatus(indexStatus)) parsed.staged += 1;
12436
- if (worktreeStatus === "M" || worktreeStatus === "T") parsed.modified += 1;
12437
- if (indexStatus === "D" || worktreeStatus === "D") parsed.deleted += 1;
12438
- if (indexStatus === "R" || worktreeStatus === "R") parsed.renamed += 1;
12439
- if (xy.includes("U")) {
12440
- const filePath = fields.slice(line.startsWith("2 ") ? 9 : 8).join(" ").split(" ")[0] ?? "";
12441
- if (filePath) parsed.conflictFiles.push(filePath);
12442
- }
12443
- }
12444
- }
12445
- parsed.conflictFiles = Array.from(new Set(parsed.conflictFiles));
12446
- return parsed;
12447
- }
12448
- async function readHead(repo, options) {
12449
- try {
12450
- const result = await runGit(repo, ["log", "-1", "--pretty=%h%x00%s"], options);
12451
- const text = result.stdout.trimEnd();
12452
- if (!text) return { commit: null, message: null };
12453
- const [commit, ...messageParts] = text.split("\0");
12454
- return {
12455
- commit: commit || null,
12456
- message: messageParts.join("\0") || null
12457
- };
12458
- } catch {
12459
- return { commit: null, message: null };
12460
- }
12461
- }
12462
- async function readStashCount(repo, options) {
12463
- try {
12464
- const result = await runGit(repo, ["stash", "list", "--format=%gd"], options);
12465
- return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
12466
- } catch {
12467
- return 0;
12468
- }
12469
- }
12470
- function isStagedStatus(status) {
12471
- return status !== "." && status !== "?" && status !== "U";
12472
- }
12473
- function emptyStatus(workspace, lastCheckedAt, error) {
12474
- return {
12475
- workspace,
12476
- repoRoot: null,
12477
- isGitRepo: false,
12478
- branch: null,
12479
- headCommit: null,
12480
- headMessage: null,
12481
- upstream: null,
12482
- upstreamStatus: "unavailable",
12483
- ahead: 0,
12484
- behind: 0,
12485
- staged: 0,
12486
- modified: 0,
12487
- untracked: 0,
12488
- deleted: 0,
12489
- renamed: 0,
12490
- hasConflicts: false,
12491
- conflictFiles: [],
12492
- stashCount: 0,
12493
- lastCheckedAt,
12494
- error: error.stderr || error.message,
12495
- reason: error.reason
12496
- };
12497
- }
12498
- async function getSubmoduleStatuses(repo, options) {
12499
- if (!repo.repoRoot) return [];
12500
- try {
12501
- const result = await runGit(repo, ["submodule", "status", "--recursive"], options);
12502
- return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
12503
- } catch {
12504
- return [];
12505
- }
12506
- }
12507
- function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
12508
- const submodules = [];
12509
- const ignoreSet = new Set(ignorePaths || []);
12510
- for (const line of output.split("\n")) {
12511
- if (!line.trim()) continue;
12512
- const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
12513
- if (!match) continue;
12514
- const prefix = match[1];
12515
- const commit = match[2];
12516
- const path40 = match[3];
12517
- if (ignoreSet.has(path40)) continue;
12518
- submodules.push({
12519
- path: path40,
12520
- commit,
12521
- repoPath: repoRoot + "/" + path40,
12522
- dirty: prefix === "+",
12523
- outOfSync: prefix === "-",
12524
- lastCheckedAt: Date.now()
12525
- });
12526
- }
12527
- return submodules;
12528
- }
13007
+ init_git_status();
12529
13008
 
12530
13009
  // src/git/git-diff.ts
12531
13010
  init_git_executor();
@@ -12941,6 +13420,7 @@ function createGitSnapshotStore(options = {}) {
12941
13420
  }
12942
13421
 
12943
13422
  // src/git/git-monitor.ts
13423
+ init_git_status();
12944
13424
  var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
12945
13425
  var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
12946
13426
  function defaultStatusProvider(workspace) {
@@ -13063,6 +13543,7 @@ function createGitWorkspaceMonitor(options = {}) {
13063
13543
  // src/git/git-commands.ts
13064
13544
  import * as path3 from "path";
13065
13545
  init_git_executor();
13546
+ init_git_status();
13066
13547
  var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
13067
13548
  "git_status",
13068
13549
  "git_diff_summary",
@@ -14543,414 +15024,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
14543
15024
  return state;
14544
15025
  }
14545
15026
 
14546
- // src/mesh/mesh-sync.ts
14547
- init_mesh_config();
14548
- async function syncMeshes(transport) {
14549
- const result = { pushed: 0, pulled: 0, deleted: 0, errors: [] };
14550
- let remoteMeshes;
14551
- try {
14552
- const res = await transport.listRemoteMeshes();
14553
- remoteMeshes = res.meshes;
14554
- } catch (e) {
14555
- result.errors.push(`Failed to list remote meshes: ${e.message}`);
14556
- return result;
14557
- }
14558
- const localMeshes = listMeshes();
14559
- const remoteByIdentity = new Map(remoteMeshes.map((m) => [m.repo_identity, m]));
14560
- const localByIdentity = new Map(localMeshes.map((m) => [m.repoIdentity, m]));
14561
- for (const local of localMeshes) {
14562
- if (!remoteByIdentity.has(local.repoIdentity)) {
14563
- try {
14564
- await transport.createRemoteMesh({
14565
- name: local.name,
14566
- repo_identity: local.repoIdentity,
14567
- repo_remote_url: local.repoRemoteUrl,
14568
- default_branch: local.defaultBranch,
14569
- policy: JSON.stringify(local.policy)
14570
- });
14571
- result.pushed++;
14572
- } catch (e) {
14573
- result.errors.push(`Push failed for "${local.name}": ${e.message}`);
14574
- }
14575
- }
14576
- }
14577
- for (const remote of remoteMeshes) {
14578
- if (!localByIdentity.has(remote.repo_identity)) {
14579
- try {
14580
- let policy;
14581
- try {
14582
- policy = JSON.parse(remote.policy);
14583
- } catch {
14584
- policy = void 0;
14585
- }
14586
- createMesh({
14587
- name: remote.name,
14588
- repoIdentity: remote.repo_identity,
14589
- repoRemoteUrl: remote.repo_remote_url || void 0,
14590
- defaultBranch: remote.default_branch || void 0,
14591
- policy
14592
- });
14593
- result.pulled++;
14594
- } catch (e) {
14595
- result.errors.push(`Pull failed for "${remote.name}": ${e.message}`);
14596
- }
14597
- }
14598
- }
14599
- return result;
14600
- }
14601
-
14602
15027
  // src/index.ts
14603
15028
  init_mesh_ledger();
14604
-
14605
- // src/mesh/mesh-fast-forward.ts
14606
- init_git_executor();
14607
- var STATUS_OPTIONS = { refreshUpstream: true, includeSubmodules: true, timeoutMs: 15e3 };
14608
- async function fastForwardMeshNode(args) {
14609
- const workspace = typeof args.workspace === "string" ? args.workspace.trim() : "";
14610
- const nodeId = normalizeOptionalString(args.nodeId);
14611
- const meshId = normalizeOptionalString(args.meshId);
14612
- const requestedBranch = normalizeOptionalString(args.branch);
14613
- const updateSubmodules = args.updateSubmodules === true;
14614
- const dryRun = args.dryRun === true || args.execute !== true;
14615
- const plannedSteps = buildPlannedSteps(updateSubmodules);
14616
- const base = {
14617
- ...nodeId ? { nodeId } : {},
14618
- ...meshId ? { meshId } : {},
14619
- workspace,
14620
- dryRun,
14621
- updateSubmodules,
14622
- plannedSteps
14623
- };
14624
- if (!workspace) {
14625
- return block(base, "invalid_workspace", ["workspace_required"]);
14626
- }
14627
- const current = await getGitRepoStatus(workspace, {
14628
- ...STATUS_OPTIONS,
14629
- submoduleIgnorePaths: args.submoduleIgnorePaths,
14630
- timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
14631
- });
14632
- const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
14633
- if (earlyBlockers.length > 0) {
14634
- return {
14635
- ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
14636
- current,
14637
- finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
14638
- };
14639
- }
14640
- if (current.behind === 0) {
14641
- const result2 = {
14642
- ...base,
14643
- success: true,
14644
- code: "already_up_to_date",
14645
- allowed: true,
14646
- willRun: false,
14647
- executed: false,
14648
- blockingReasons: [],
14649
- current,
14650
- preStatus: current,
14651
- postStatus: current,
14652
- finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
14653
- };
14654
- await appendFastForwardLedger(result2, "noop");
14655
- return result2;
14656
- }
14657
- const ancestorCheck = await verifyHeadIsAncestorOfUpstream(workspace, current.upstream || "", args.timeoutMs);
14658
- if (!ancestorCheck.ok) {
14659
- const result2 = {
14660
- ...block(base, "non_fast_forward", ["head_is_not_ancestor_of_upstream"]),
14661
- current,
14662
- preStatus: current,
14663
- operationError: ancestorCheck.error,
14664
- finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
14665
- };
14666
- await appendFastForwardLedger(result2, "blocked");
14667
- return result2;
14668
- }
14669
- if (dryRun) {
14670
- const result2 = {
14671
- ...base,
14672
- success: true,
14673
- code: "fast_forward_available",
14674
- allowed: true,
14675
- willRun: false,
14676
- executed: false,
14677
- blockingReasons: [],
14678
- current,
14679
- preStatus: current,
14680
- finalBranchConvergenceState: buildConvergenceState(current, "fast_forward_available")
14681
- };
14682
- return result2;
14683
- }
14684
- try {
14685
- await runGit(workspace, ["merge", "--ff-only", current.upstream || ""], { timeoutMs: args.timeoutMs ?? 3e4 });
14686
- } catch (error) {
14687
- const result2 = {
14688
- ...block(base, "merge_ff_only_failed", ["merge_ff_only_failed"]),
14689
- current,
14690
- preStatus: current,
14691
- operationError: formatGitError2(error),
14692
- finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
14693
- };
14694
- await appendFastForwardLedger(result2, "failed");
14695
- return result2;
14696
- }
14697
- let postStatus = await getGitRepoStatus(workspace, {
14698
- ...STATUS_OPTIONS,
14699
- submoduleIgnorePaths: args.submoduleIgnorePaths,
14700
- timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
14701
- });
14702
- const submoduleIssues = collectSubmoduleBlockers(postStatus, "post");
14703
- let submoduleFollowUpRequired = false;
14704
- let operationError;
14705
- if (submoduleIssues.length > 0) {
14706
- if (updateSubmodules) {
14707
- try {
14708
- await runGit(workspace, ["submodule", "update", "--init", "--recursive"], { timeoutMs: args.timeoutMs ?? 6e4 });
14709
- postStatus = await getGitRepoStatus(workspace, {
14710
- ...STATUS_OPTIONS,
14711
- submoduleIgnorePaths: args.submoduleIgnorePaths,
14712
- timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
14713
- });
14714
- } catch (error) {
14715
- operationError = formatGitError2(error);
14716
- }
14717
- } else {
14718
- submoduleFollowUpRequired = true;
14719
- }
14720
- }
14721
- const postBlockers = collectPostExecutionBlockers(postStatus);
14722
- if (operationError) postBlockers.push("submodule_update_failed");
14723
- if (submoduleFollowUpRequired) postBlockers.push("submodule_update_required");
14724
- const success = postBlockers.length === 0 || submoduleFollowUpRequired;
14725
- const code = postBlockers.length === 0 ? "fast_forward_applied" : submoduleFollowUpRequired ? "fast_forward_applied_submodule_update_required" : "post_verify_failed";
14726
- const result = {
14727
- ...base,
14728
- success,
14729
- code,
14730
- allowed: true,
14731
- willRun: true,
14732
- executed: true,
14733
- blockingReasons: postBlockers,
14734
- current,
14735
- preStatus: current,
14736
- postStatus,
14737
- ...operationError ? { operationError } : {},
14738
- finalBranchConvergenceState: buildConvergenceState(
14739
- postStatus,
14740
- postBlockers.length === 0 ? "fast_forwarded" : submoduleFollowUpRequired ? "follow_up_required" : "post_verify_failed"
14741
- )
14742
- };
14743
- await appendFastForwardLedger(result, success ? "executed" : "failed");
14744
- return result;
14745
- }
14746
- function buildPlannedSteps(updateSubmodules) {
14747
- const steps = [
14748
- {
14749
- operation: "refresh_upstream",
14750
- description: "Refresh the tracked upstream remote ref before trusting ahead/behind state.",
14751
- safe: true,
14752
- willMutateWorktree: false
14753
- },
14754
- {
14755
- operation: "verify_clean_worktree",
14756
- description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
14757
- safe: true,
14758
- willMutateWorktree: false
14759
- },
14760
- {
14761
- operation: "verify_fast_forward",
14762
- description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
14763
- safe: true,
14764
- willMutateWorktree: false
14765
- },
14766
- {
14767
- operation: "merge_ff_only",
14768
- description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
14769
- safe: true,
14770
- willMutateWorktree: true
14771
- }
14772
- ];
14773
- if (updateSubmodules) {
14774
- steps.push({
14775
- operation: "submodule_update",
14776
- description: "If the fast-forward changes gitlinks, run git submodule update --init --recursive and re-verify submodules.",
14777
- safe: true,
14778
- willMutateWorktree: true
14779
- });
14780
- }
14781
- steps.push({
14782
- operation: "verify_post_status",
14783
- description: "Re-read daemon-owned git status and report final branch convergence state.",
14784
- safe: true,
14785
- willMutateWorktree: false
14786
- });
14787
- return steps;
14788
- }
14789
- function collectPreflightBlockers(status, requestedBranch) {
14790
- const blockers = [];
14791
- if (!status.isGitRepo) blockers.push("not_git_repo");
14792
- if (!status.branch) blockers.push("detached_head_or_unknown_branch");
14793
- if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
14794
- if (!status.upstream) blockers.push("upstream_missing");
14795
- if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
14796
- if (status.hasConflicts) blockers.push("conflicts_present");
14797
- if (status.staged > 0) blockers.push("staged_changes_present");
14798
- if (status.modified > 0) blockers.push("modified_changes_present");
14799
- if (status.untracked > 0) blockers.push("untracked_changes_present");
14800
- if (status.deleted > 0) blockers.push("deleted_changes_present");
14801
- if (status.renamed > 0) blockers.push("renamed_changes_present");
14802
- if (status.stashCount > 0) blockers.push("stash_entries_present");
14803
- blockers.push(...collectSubmoduleBlockers(status, "pre"));
14804
- if (status.ahead > 0 && status.behind > 0) {
14805
- blockers.push("branch_diverged_from_upstream");
14806
- blockers.push("branch_has_local_commits");
14807
- } else if (status.ahead > 0) blockers.push("branch_has_local_commits");
14808
- return blockers;
14809
- }
14810
- function collectPostExecutionBlockers(status) {
14811
- const blockers = [];
14812
- if (!status.isGitRepo) blockers.push("post_not_git_repo");
14813
- if (status.hasConflicts) blockers.push("post_conflicts_present");
14814
- if (status.ahead !== 0) blockers.push("post_branch_ahead");
14815
- if (status.behind !== 0) blockers.push("post_branch_still_behind");
14816
- if (status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0) {
14817
- blockers.push("post_working_tree_not_clean");
14818
- }
14819
- if (status.stashCount > 0) blockers.push("post_stash_entries_present");
14820
- blockers.push(...collectSubmoduleBlockers(status, "post"));
14821
- return blockers;
14822
- }
14823
- function collectSubmoduleBlockers(status, phase) {
14824
- const submodules = Array.isArray(status.submodules) ? status.submodules : [];
14825
- const blockers = [];
14826
- for (const submodule of submodules) {
14827
- if (submodule.error) blockers.push(`${phase}_submodule_status_error:${submodule.path}`);
14828
- if (submodule.dirty) blockers.push(`${phase}_submodule_dirty:${submodule.path}`);
14829
- if (submodule.outOfSync) blockers.push(`${phase}_submodule_out_of_sync:${submodule.path}`);
14830
- }
14831
- return blockers;
14832
- }
14833
- function chooseBlockCode(status, blockers) {
14834
- if (blockers.includes("not_git_repo")) return "not_git_repo";
14835
- if (blockers.includes("branch_mismatch")) return "branch_mismatch";
14836
- if (blockers.includes("upstream_missing")) return "upstream_missing";
14837
- if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
14838
- if (blockers.some((reason) => reason.includes("submodule"))) return "submodule_not_clean";
14839
- if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
14840
- if (blockers.includes("branch_has_local_commits") || status.ahead > 0) return "branch_ahead";
14841
- if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
14842
- return "preflight_blocked";
14843
- }
14844
- function codeToConvergenceStatus(code) {
14845
- if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
14846
- if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
14847
- return "blocked";
14848
- }
14849
- async function verifyHeadIsAncestorOfUpstream(workspace, upstream, timeoutMs) {
14850
- if (!upstream) return { ok: false, error: "missing upstream" };
14851
- try {
14852
- await runGit(workspace, ["merge-base", "--is-ancestor", "HEAD", upstream], { timeoutMs: timeoutMs ?? 15e3 });
14853
- return { ok: true };
14854
- } catch (error) {
14855
- return { ok: false, error: formatGitError2(error) };
14856
- }
14857
- }
14858
- function block(base, code, blockingReasons) {
14859
- const normalizedReasons = normalizeBlockingReasons(blockingReasons);
14860
- return {
14861
- ...base,
14862
- success: false,
14863
- code,
14864
- allowed: false,
14865
- willRun: false,
14866
- executed: false,
14867
- blockingReasons: normalizedReasons
14868
- };
14869
- }
14870
- function normalizeBlockingReasons(reasons) {
14871
- const normalized = /* @__PURE__ */ new Set();
14872
- for (const reason of reasons) {
14873
- normalized.add(reason);
14874
- }
14875
- if ([
14876
- "conflicts_present",
14877
- "staged_changes_present",
14878
- "modified_changes_present",
14879
- "untracked_changes_present",
14880
- "deleted_changes_present",
14881
- "renamed_changes_present"
14882
- ].some((reason) => normalized.has(reason))) {
14883
- normalized.add("working_tree_not_clean");
14884
- }
14885
- return Array.from(normalized);
14886
- }
14887
- function buildConvergenceState(status, convergenceStatus) {
14888
- return {
14889
- status: convergenceStatus,
14890
- branch: status.branch,
14891
- headCommit: status.headCommit,
14892
- upstream: status.upstream,
14893
- ahead: status.ahead,
14894
- behind: status.behind,
14895
- dirty: status.staged + status.modified + status.untracked + status.deleted + status.renamed > 0 || status.hasConflicts,
14896
- stashCount: status.stashCount,
14897
- submodules: summarizeSubmodules(status.submodules)
14898
- };
14899
- }
14900
- function summarizeSubmodules(submodules) {
14901
- return (submodules || []).map((submodule) => ({
14902
- path: submodule.path,
14903
- commit: submodule.commit,
14904
- dirty: submodule.dirty,
14905
- outOfSync: submodule.outOfSync,
14906
- ...submodule.error ? { error: submodule.error } : {}
14907
- }));
14908
- }
14909
- function normalizeOptionalString(value) {
14910
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
14911
- }
14912
- function formatGitError2(error) {
14913
- if (error instanceof GitCommandError) {
14914
- return error.stderr || error.stdout || error.message;
14915
- }
14916
- if (error instanceof Error) return error.message;
14917
- return String(error);
14918
- }
14919
- async function appendFastForwardLedger(result, outcome) {
14920
- if (!result.meshId) return;
14921
- try {
14922
- const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
14923
- appendLedgerEntry2(result.meshId, {
14924
- kind: "direct_fast_forward",
14925
- ...result.nodeId ? { nodeId: result.nodeId } : {},
14926
- payload: {
14927
- operation: "mesh_fast_forward_node",
14928
- outcome,
14929
- code: result.code,
14930
- workspace: result.workspace,
14931
- allowed: result.allowed,
14932
- dryRun: result.dryRun,
14933
- willRun: result.willRun,
14934
- executed: result.executed,
14935
- branch: result.postStatus?.branch ?? result.current?.branch,
14936
- upstream: result.postStatus?.upstream ?? result.current?.upstream,
14937
- before: result.current ? {
14938
- headCommit: result.current.headCommit,
14939
- ahead: result.current.ahead,
14940
- behind: result.current.behind
14941
- } : void 0,
14942
- after: result.postStatus ? {
14943
- headCommit: result.postStatus.headCommit,
14944
- ahead: result.postStatus.ahead,
14945
- behind: result.postStatus.behind
14946
- } : void 0,
14947
- blockingReasons: result.blockingReasons
14948
- }
14949
- });
14950
- } catch (error) {
14951
- result.ledgerError = error instanceof Error ? error.message : String(error);
14952
- }
14953
- }
15029
+ init_mesh_fast_forward();
14954
15030
 
14955
15031
  // src/mesh/mesh-ledger-reconciliation.ts
14956
15032
  function lastTimestamp(slice) {
@@ -34416,6 +34492,7 @@ function getAvailableIdeIds() {
34416
34492
  // src/commands/router.ts
34417
34493
  init_config();
34418
34494
  init_cli_detector();
34495
+ init_git_status();
34419
34496
  init_logger();
34420
34497
 
34421
34498
  // src/logging/command-log.ts
@@ -34568,6 +34645,7 @@ import * as yaml3 from "js-yaml";
34568
34645
  init_mesh_coordinator();
34569
34646
  init_mesh_events();
34570
34647
  init_mesh_host_ownership();
34648
+ init_mesh_fast_forward();
34571
34649
 
34572
34650
  // src/mesh/preview-freshness.ts
34573
34651
  import { execFileSync as execFileSync3 } from "child_process";
@@ -37222,8 +37300,8 @@ var DaemonCommandRouter = class {
37222
37300
  }
37223
37301
  }
37224
37302
  try {
37225
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37226
- const mesh = getMesh3(meshId);
37303
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37304
+ const mesh = getMesh2(meshId);
37227
37305
  if (mesh) return { mesh, inline: false, source: "local_config" };
37228
37306
  } catch {
37229
37307
  }
@@ -39148,8 +39226,8 @@ var DaemonCommandRouter = class {
39148
39226
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
39149
39227
  if (!meshId) return { success: false, error: "meshId required" };
39150
39228
  try {
39151
- const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39152
- const deleted = deleteMesh3(meshId);
39229
+ const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39230
+ const deleted = deleteMesh2(meshId);
39153
39231
  return { success: true, deleted };
39154
39232
  } catch (e) {
39155
39233
  return { success: false, error: e.message };
@@ -39267,7 +39345,7 @@ var DaemonCommandRouter = class {
39267
39345
  const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
39268
39346
  if (ownerFailure) return ownerFailure;
39269
39347
  try {
39270
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39348
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39271
39349
  const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
39272
39350
  const readOnly = args?.readOnly === true;
39273
39351
  const policy = {
@@ -39278,7 +39356,7 @@ var DaemonCommandRouter = class {
39278
39356
  const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
39279
39357
  const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
39280
39358
  const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
39281
- const node = addNode3(meshId, {
39359
+ const node = addNode2(meshId, {
39282
39360
  workspace,
39283
39361
  ...repoRoot ? { repoRoot } : {},
39284
39362
  ...daemonId ? { daemonId } : {},
@@ -39469,8 +39547,8 @@ var DaemonCommandRouter = class {
39469
39547
  if (meshRecord?.inline) {
39470
39548
  removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
39471
39549
  } else {
39472
- const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39473
- removed = removeNode3(meshId, nodeId);
39550
+ const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39551
+ removed = removeNode2(meshId, nodeId);
39474
39552
  if (removed) this.invalidateAggregateMeshStatus(meshId);
39475
39553
  }
39476
39554
  if (removed) {
@@ -39539,8 +39617,8 @@ var DaemonCommandRouter = class {
39539
39617
  };
39540
39618
  this.updateInlineMeshNode(meshId, mesh, node);
39541
39619
  } else {
39542
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39543
- node = addNode3(meshId, {
39620
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39621
+ node = addNode2(meshId, {
39544
39622
  workspace: result.worktreePath,
39545
39623
  repoRoot: result.worktreePath,
39546
39624
  daemonId: sourceNode.daemonId,
@@ -39695,8 +39773,8 @@ var DaemonCommandRouter = class {
39695
39773
  mesh = args.inlineMesh;
39696
39774
  this.inlineMeshCache.set(meshId, mesh);
39697
39775
  } else {
39698
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39699
- mesh = getMesh3(meshId);
39776
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39777
+ mesh = getMesh2(meshId);
39700
39778
  }
39701
39779
  if (!mesh) return { success: false, error: "Mesh not found" };
39702
39780
  const meshHost = resolveMeshHostStatus(mesh);
@@ -49074,6 +49152,7 @@ export {
49074
49152
  buildMeshHostRequiredFailure,
49075
49153
  buildMeshLedgerReconciliationEvidence,
49076
49154
  buildMeshLedgerReplicaEvidence,
49155
+ buildMeshNodeCapabilityTags,
49077
49156
  buildP2pRelayFailurePayload,
49078
49157
  buildPinnedGlobalInstallCommand,
49079
49158
  buildRuntimeSystemChatMessage,
@@ -49201,6 +49280,7 @@ export {
49201
49280
  maybeRunDaemonUpgradeHelperFromEnv,
49202
49281
  namedKeyToAnsi,
49203
49282
  namedKeysToAnsi,
49283
+ nodeSatisfiesRequiredTags,
49204
49284
  normalizeActiveChatData,
49205
49285
  normalizeChatMessage,
49206
49286
  normalizeChatMessageKind,
@@ -49212,6 +49292,7 @@ export {
49212
49292
  normalizeInteractivePrompt,
49213
49293
  normalizeInteractivePromptResponse,
49214
49294
  normalizeManagedStatus,
49295
+ normalizeMeshCapabilityTags,
49215
49296
  normalizeMeshDaemonRole,
49216
49297
  normalizeMeshTaskMode,
49217
49298
  normalizeMeshWorkerResult,
@@ -49270,7 +49351,6 @@ export {
49270
49351
  startLocalIpcServer,
49271
49352
  suggestMeshRefineConfig,
49272
49353
  summarizeGitStatus,
49273
- syncMeshes,
49274
49354
  triggerMeshQueue,
49275
49355
  unregisterMeshCoordinator,
49276
49356
  updateConfig,