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

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;
@@ -10655,13 +11385,15 @@ function executeJsonl(src, input) {
10655
11385
  try {
10656
11386
  stat2 = fs13.statSync(resolved);
10657
11387
  } catch {
10658
- return null;
10659
11388
  }
10660
- if (stat2.isFile()) {
11389
+ if (stat2 && stat2.isFile()) {
10661
11390
  sourcePath = resolved;
10662
- } else if (stat2.isDirectory()) {
11391
+ } else if (stat2 && stat2.isDirectory()) {
10663
11392
  sourcePath = newestRecentFile(resolved, filePat, windowMs, sessionFloor);
10664
11393
  }
11394
+ if (!sourcePath && hasDateTemplateSegment(src.path)) {
11395
+ sourcePath = newestRecentFileAcrossDateWindow(src.path, input, filePat, windowMs, sessionFloor);
11396
+ }
10665
11397
  }
10666
11398
  if (!sourcePath) return null;
10667
11399
  const mtime = safeMtimeMs(sourcePath);
@@ -10884,6 +11616,69 @@ function newestRecentFileAcrossGlob(template, pattern, windowMs, sessionFloorMs
10884
11616
  }
10885
11617
  return best ? best.p : null;
10886
11618
  }
11619
+ function hasDateTemplateSegment(template) {
11620
+ return /\{yyyy\}|\{mm\}|\{dd\}/.test(template);
11621
+ }
11622
+ function newestRecentFileAcrossDateWindow(template, input, pattern, windowMs, sessionFloorMs) {
11623
+ const cutoff = Math.max(Date.now() - windowMs, sessionFloorMs);
11624
+ let best = null;
11625
+ for (let dayOffset = 0; dayOffset < 3; dayOffset += 1) {
11626
+ const dayMs = Date.now() - dayOffset * 24 * 60 * 60 * 1e3;
11627
+ const dayInput = { ...input, sessionStartedAtMs: sessionFloorMs };
11628
+ const resolved = expandPathForDate(template, dayInput, new Date(dayMs));
11629
+ if (!resolved) continue;
11630
+ let entries;
11631
+ try {
11632
+ entries = fs13.readdirSync(resolved, { withFileTypes: true });
11633
+ } catch {
11634
+ continue;
11635
+ }
11636
+ for (const e of entries) {
11637
+ if (!e.isFile() || !pattern.test(e.name)) continue;
11638
+ const p = path25.join(resolved, e.name);
11639
+ const mtime = safeMtimeMs(p);
11640
+ if (mtime < cutoff) continue;
11641
+ if (!best || mtime > best.mtime) best = { p, mtime };
11642
+ }
11643
+ }
11644
+ return best ? best.p : null;
11645
+ }
11646
+ function expandPathForDate(template, input, day) {
11647
+ if (!template) return null;
11648
+ let out = template;
11649
+ if (out.startsWith("~/") || out === "~") {
11650
+ out = path25.join(os18.homedir(), out.slice(2));
11651
+ }
11652
+ out = out.replace(/\$\{([A-Z_][A-Z0-9_]*)(?::-(.*?))?\}/g, (_m, name, fallback) => {
11653
+ const v = input.envOverrides?.[name] ?? process.env[name];
11654
+ return v != null && v !== "" ? v : fallback ?? "";
11655
+ });
11656
+ if (out.startsWith("~/")) out = path25.join(os18.homedir(), out.slice(2));
11657
+ const workspaceRaw = input.workspace ?? "";
11658
+ let workspaceResolved = workspaceRaw;
11659
+ if (workspaceRaw) {
11660
+ try {
11661
+ workspaceResolved = fs13.realpathSync(workspaceRaw);
11662
+ } catch {
11663
+ }
11664
+ }
11665
+ const vars = {
11666
+ cwd: workspaceResolved,
11667
+ cwd_dashed: workspaceResolved.replace(/\//g, "-"),
11668
+ session_id: input.providerSessionId || input.sessionId || input.historySessionId || "",
11669
+ yyyy: String(day.getUTCFullYear()),
11670
+ mm: String(day.getUTCMonth() + 1).padStart(2, "0"),
11671
+ dd: String(day.getUTCDate()).padStart(2, "0")
11672
+ };
11673
+ let missing = false;
11674
+ out = out.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_m, name) => {
11675
+ const v = vars[name] ?? "";
11676
+ if (!v) missing = true;
11677
+ return v;
11678
+ });
11679
+ if (missing) return null;
11680
+ return out;
11681
+ }
10887
11682
  function newestRecentFile(dir, pattern, windowMs, sessionFloorMs = 0) {
10888
11683
  let entries;
10889
11684
  try {
@@ -12143,6 +12938,7 @@ __export(index_exports, {
12143
12938
  buildMeshHostRequiredFailure: () => buildMeshHostRequiredFailure,
12144
12939
  buildMeshLedgerReconciliationEvidence: () => buildMeshLedgerReconciliationEvidence,
12145
12940
  buildMeshLedgerReplicaEvidence: () => buildMeshLedgerReplicaEvidence,
12941
+ buildMeshNodeCapabilityTags: () => buildMeshNodeCapabilityTags,
12146
12942
  buildP2pRelayFailurePayload: () => buildP2pRelayFailurePayload,
12147
12943
  buildPinnedGlobalInstallCommand: () => buildPinnedGlobalInstallCommand,
12148
12944
  buildRuntimeSystemChatMessage: () => buildRuntimeSystemChatMessage,
@@ -12270,6 +13066,7 @@ __export(index_exports, {
12270
13066
  maybeRunDaemonUpgradeHelperFromEnv: () => maybeRunDaemonUpgradeHelperFromEnv,
12271
13067
  namedKeyToAnsi: () => namedKeyToAnsi,
12272
13068
  namedKeysToAnsi: () => namedKeysToAnsi,
13069
+ nodeSatisfiesRequiredTags: () => nodeSatisfiesRequiredTags,
12273
13070
  normalizeActiveChatData: () => normalizeActiveChatData,
12274
13071
  normalizeChatMessage: () => normalizeChatMessage,
12275
13072
  normalizeChatMessageKind: () => normalizeChatMessageKind,
@@ -12281,6 +13078,7 @@ __export(index_exports, {
12281
13078
  normalizeInteractivePrompt: () => normalizeInteractivePrompt,
12282
13079
  normalizeInteractivePromptResponse: () => normalizeInteractivePromptResponse,
12283
13080
  normalizeManagedStatus: () => normalizeManagedStatus,
13081
+ normalizeMeshCapabilityTags: () => normalizeMeshCapabilityTags,
12284
13082
  normalizeMeshDaemonRole: () => normalizeMeshDaemonRole,
12285
13083
  normalizeMeshTaskMode: () => normalizeMeshTaskMode,
12286
13084
  normalizeMeshWorkerResult: () => normalizeMeshWorkerResult,
@@ -12339,7 +13137,6 @@ __export(index_exports, {
12339
13137
  startLocalIpcServer: () => startLocalIpcServer,
12340
13138
  suggestMeshRefineConfig: () => suggestMeshRefineConfig,
12341
13139
  summarizeGitStatus: () => summarizeGitStatus,
12342
- syncMeshes: () => syncMeshes,
12343
13140
  triggerMeshQueue: () => triggerMeshQueue,
12344
13141
  unregisterMeshCoordinator: () => unregisterMeshCoordinator,
12345
13142
  updateConfig: () => updateConfig,
@@ -12582,258 +13379,7 @@ init_repo_mesh_types();
12582
13379
 
12583
13380
  // src/git/index.ts
12584
13381
  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
- }
13382
+ init_git_status();
12837
13383
 
12838
13384
  // src/git/git-diff.ts
12839
13385
  var import_promises2 = require("fs/promises");
@@ -13249,6 +13795,7 @@ function createGitSnapshotStore(options = {}) {
13249
13795
  }
13250
13796
 
13251
13797
  // src/git/git-monitor.ts
13798
+ init_git_status();
13252
13799
  var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
13253
13800
  var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
13254
13801
  function defaultStatusProvider(workspace) {
@@ -13371,6 +13918,7 @@ function createGitWorkspaceMonitor(options = {}) {
13371
13918
  // src/git/git-commands.ts
13372
13919
  var path3 = __toESM(require("path"));
13373
13920
  init_git_executor();
13921
+ init_git_status();
13374
13922
  var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
13375
13923
  "git_status",
13376
13924
  "git_diff_summary",
@@ -14851,414 +15399,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
14851
15399
  return state;
14852
15400
  }
14853
15401
 
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
15402
  // src/index.ts
14911
15403
  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
- }
15404
+ init_mesh_fast_forward();
15262
15405
 
15263
15406
  // src/mesh/mesh-ledger-reconciliation.ts
15264
15407
  function lastTimestamp(slice) {
@@ -34719,6 +34862,7 @@ function getAvailableIdeIds() {
34719
34862
  // src/commands/router.ts
34720
34863
  init_config();
34721
34864
  init_cli_detector();
34865
+ init_git_status();
34722
34866
  init_logger();
34723
34867
 
34724
34868
  // src/logging/command-log.ts
@@ -34871,6 +35015,7 @@ init_logger();
34871
35015
  init_mesh_coordinator();
34872
35016
  init_mesh_events();
34873
35017
  init_mesh_host_ownership();
35018
+ init_mesh_fast_forward();
34874
35019
 
34875
35020
  // src/mesh/preview-freshness.ts
34876
35021
  var import_node_child_process4 = require("child_process");
@@ -37525,8 +37670,8 @@ var DaemonCommandRouter = class {
37525
37670
  }
37526
37671
  }
37527
37672
  try {
37528
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37529
- const mesh = getMesh3(meshId);
37673
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37674
+ const mesh = getMesh2(meshId);
37530
37675
  if (mesh) return { mesh, inline: false, source: "local_config" };
37531
37676
  } catch {
37532
37677
  }
@@ -39451,8 +39596,8 @@ var DaemonCommandRouter = class {
39451
39596
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
39452
39597
  if (!meshId) return { success: false, error: "meshId required" };
39453
39598
  try {
39454
- const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39455
- const deleted = deleteMesh3(meshId);
39599
+ const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39600
+ const deleted = deleteMesh2(meshId);
39456
39601
  return { success: true, deleted };
39457
39602
  } catch (e) {
39458
39603
  return { success: false, error: e.message };
@@ -39570,7 +39715,7 @@ var DaemonCommandRouter = class {
39570
39715
  const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
39571
39716
  if (ownerFailure) return ownerFailure;
39572
39717
  try {
39573
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39718
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39574
39719
  const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
39575
39720
  const readOnly = args?.readOnly === true;
39576
39721
  const policy = {
@@ -39581,7 +39726,7 @@ var DaemonCommandRouter = class {
39581
39726
  const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
39582
39727
  const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
39583
39728
  const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
39584
- const node = addNode3(meshId, {
39729
+ const node = addNode2(meshId, {
39585
39730
  workspace,
39586
39731
  ...repoRoot ? { repoRoot } : {},
39587
39732
  ...daemonId ? { daemonId } : {},
@@ -39772,8 +39917,8 @@ var DaemonCommandRouter = class {
39772
39917
  if (meshRecord?.inline) {
39773
39918
  removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
39774
39919
  } else {
39775
- const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39776
- removed = removeNode3(meshId, nodeId);
39920
+ const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39921
+ removed = removeNode2(meshId, nodeId);
39777
39922
  if (removed) this.invalidateAggregateMeshStatus(meshId);
39778
39923
  }
39779
39924
  if (removed) {
@@ -39842,8 +39987,8 @@ var DaemonCommandRouter = class {
39842
39987
  };
39843
39988
  this.updateInlineMeshNode(meshId, mesh, node);
39844
39989
  } else {
39845
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39846
- node = addNode3(meshId, {
39990
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39991
+ node = addNode2(meshId, {
39847
39992
  workspace: result.worktreePath,
39848
39993
  repoRoot: result.worktreePath,
39849
39994
  daemonId: sourceNode.daemonId,
@@ -39998,8 +40143,8 @@ var DaemonCommandRouter = class {
39998
40143
  mesh = args.inlineMesh;
39999
40144
  this.inlineMeshCache.set(meshId, mesh);
40000
40145
  } else {
40001
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
40002
- mesh = getMesh3(meshId);
40146
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
40147
+ mesh = getMesh2(meshId);
40003
40148
  }
40004
40149
  if (!mesh) return { success: false, error: "Mesh not found" };
40005
40150
  const meshHost = resolveMeshHostStatus(mesh);
@@ -49371,6 +49516,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
49371
49516
  buildMeshHostRequiredFailure,
49372
49517
  buildMeshLedgerReconciliationEvidence,
49373
49518
  buildMeshLedgerReplicaEvidence,
49519
+ buildMeshNodeCapabilityTags,
49374
49520
  buildP2pRelayFailurePayload,
49375
49521
  buildPinnedGlobalInstallCommand,
49376
49522
  buildRuntimeSystemChatMessage,
@@ -49498,6 +49644,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
49498
49644
  maybeRunDaemonUpgradeHelperFromEnv,
49499
49645
  namedKeyToAnsi,
49500
49646
  namedKeysToAnsi,
49647
+ nodeSatisfiesRequiredTags,
49501
49648
  normalizeActiveChatData,
49502
49649
  normalizeChatMessage,
49503
49650
  normalizeChatMessageKind,
@@ -49509,6 +49656,7 @@ var V1_CONTRACT_VERSION = "1.0.0";
49509
49656
  normalizeInteractivePrompt,
49510
49657
  normalizeInteractivePromptResponse,
49511
49658
  normalizeManagedStatus,
49659
+ normalizeMeshCapabilityTags,
49512
49660
  normalizeMeshDaemonRole,
49513
49661
  normalizeMeshTaskMode,
49514
49662
  normalizeMeshWorkerResult,
@@ -49567,7 +49715,6 @@ var V1_CONTRACT_VERSION = "1.0.0";
49567
49715
  startLocalIpcServer,
49568
49716
  suggestMeshRefineConfig,
49569
49717
  summarizeGitStatus,
49570
- syncMeshes,
49571
49718
  triggerMeshQueue,
49572
49719
  unregisterMeshCoordinator,
49573
49720
  updateConfig,