@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.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;
@@ -10653,13 +11383,15 @@ function executeJsonl(src, input) {
10653
11383
  try {
10654
11384
  stat2 = fs13.statSync(resolved);
10655
11385
  } catch {
10656
- return null;
10657
11386
  }
10658
- if (stat2.isFile()) {
11387
+ if (stat2 && stat2.isFile()) {
10659
11388
  sourcePath = resolved;
10660
- } else if (stat2.isDirectory()) {
11389
+ } else if (stat2 && stat2.isDirectory()) {
10661
11390
  sourcePath = newestRecentFile(resolved, filePat, windowMs, sessionFloor);
10662
11391
  }
11392
+ if (!sourcePath && hasDateTemplateSegment(src.path)) {
11393
+ sourcePath = newestRecentFileAcrossDateWindow(src.path, input, filePat, windowMs, sessionFloor);
11394
+ }
10663
11395
  }
10664
11396
  if (!sourcePath) return null;
10665
11397
  const mtime = safeMtimeMs(sourcePath);
@@ -10882,6 +11614,69 @@ function newestRecentFileAcrossGlob(template, pattern, windowMs, sessionFloorMs
10882
11614
  }
10883
11615
  return best ? best.p : null;
10884
11616
  }
11617
+ function hasDateTemplateSegment(template) {
11618
+ return /\{yyyy\}|\{mm\}|\{dd\}/.test(template);
11619
+ }
11620
+ function newestRecentFileAcrossDateWindow(template, input, pattern, windowMs, sessionFloorMs) {
11621
+ const cutoff = Math.max(Date.now() - windowMs, sessionFloorMs);
11622
+ let best = null;
11623
+ for (let dayOffset = 0; dayOffset < 3; dayOffset += 1) {
11624
+ const dayMs = Date.now() - dayOffset * 24 * 60 * 60 * 1e3;
11625
+ const dayInput = { ...input, sessionStartedAtMs: sessionFloorMs };
11626
+ const resolved = expandPathForDate(template, dayInput, new Date(dayMs));
11627
+ if (!resolved) continue;
11628
+ let entries;
11629
+ try {
11630
+ entries = fs13.readdirSync(resolved, { withFileTypes: true });
11631
+ } catch {
11632
+ continue;
11633
+ }
11634
+ for (const e of entries) {
11635
+ if (!e.isFile() || !pattern.test(e.name)) continue;
11636
+ const p = path25.join(resolved, e.name);
11637
+ const mtime = safeMtimeMs(p);
11638
+ if (mtime < cutoff) continue;
11639
+ if (!best || mtime > best.mtime) best = { p, mtime };
11640
+ }
11641
+ }
11642
+ return best ? best.p : null;
11643
+ }
11644
+ function expandPathForDate(template, input, day) {
11645
+ if (!template) return null;
11646
+ let out = template;
11647
+ if (out.startsWith("~/") || out === "~") {
11648
+ out = path25.join(os18.homedir(), out.slice(2));
11649
+ }
11650
+ out = out.replace(/\$\{([A-Z_][A-Z0-9_]*)(?::-(.*?))?\}/g, (_m, name, fallback) => {
11651
+ const v = input.envOverrides?.[name] ?? process.env[name];
11652
+ return v != null && v !== "" ? v : fallback ?? "";
11653
+ });
11654
+ if (out.startsWith("~/")) out = path25.join(os18.homedir(), out.slice(2));
11655
+ const workspaceRaw = input.workspace ?? "";
11656
+ let workspaceResolved = workspaceRaw;
11657
+ if (workspaceRaw) {
11658
+ try {
11659
+ workspaceResolved = fs13.realpathSync(workspaceRaw);
11660
+ } catch {
11661
+ }
11662
+ }
11663
+ const vars = {
11664
+ cwd: workspaceResolved,
11665
+ cwd_dashed: workspaceResolved.replace(/\//g, "-"),
11666
+ session_id: input.providerSessionId || input.sessionId || input.historySessionId || "",
11667
+ yyyy: String(day.getUTCFullYear()),
11668
+ mm: String(day.getUTCMonth() + 1).padStart(2, "0"),
11669
+ dd: String(day.getUTCDate()).padStart(2, "0")
11670
+ };
11671
+ let missing = false;
11672
+ out = out.replace(/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g, (_m, name) => {
11673
+ const v = vars[name] ?? "";
11674
+ if (!v) missing = true;
11675
+ return v;
11676
+ });
11677
+ if (missing) return null;
11678
+ return out;
11679
+ }
10885
11680
  function newestRecentFile(dir, pattern, windowMs, sessionFloorMs = 0) {
10886
11681
  let entries;
10887
11682
  try {
@@ -12274,258 +13069,7 @@ init_repo_mesh_types();
12274
13069
 
12275
13070
  // src/git/index.ts
12276
13071
  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
- }
13072
+ init_git_status();
12529
13073
 
12530
13074
  // src/git/git-diff.ts
12531
13075
  init_git_executor();
@@ -12941,6 +13485,7 @@ function createGitSnapshotStore(options = {}) {
12941
13485
  }
12942
13486
 
12943
13487
  // src/git/git-monitor.ts
13488
+ init_git_status();
12944
13489
  var DEFAULT_GIT_WORKSPACE_POLL_INTERVAL_MS = 5e3;
12945
13490
  var MIN_GIT_WORKSPACE_POLL_INTERVAL_MS = 1e3;
12946
13491
  function defaultStatusProvider(workspace) {
@@ -13063,6 +13608,7 @@ function createGitWorkspaceMonitor(options = {}) {
13063
13608
  // src/git/git-commands.ts
13064
13609
  import * as path3 from "path";
13065
13610
  init_git_executor();
13611
+ init_git_status();
13066
13612
  var GIT_COMMAND_NAMES = /* @__PURE__ */ new Set([
13067
13613
  "git_status",
13068
13614
  "git_diff_summary",
@@ -14543,414 +15089,9 @@ async function runMeshWorktreeBootstrap(mesh, workspace) {
14543
15089
  return state;
14544
15090
  }
14545
15091
 
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
15092
  // src/index.ts
14603
15093
  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
- }
15094
+ init_mesh_fast_forward();
14954
15095
 
14955
15096
  // src/mesh/mesh-ledger-reconciliation.ts
14956
15097
  function lastTimestamp(slice) {
@@ -34416,6 +34557,7 @@ function getAvailableIdeIds() {
34416
34557
  // src/commands/router.ts
34417
34558
  init_config();
34418
34559
  init_cli_detector();
34560
+ init_git_status();
34419
34561
  init_logger();
34420
34562
 
34421
34563
  // src/logging/command-log.ts
@@ -34568,6 +34710,7 @@ import * as yaml3 from "js-yaml";
34568
34710
  init_mesh_coordinator();
34569
34711
  init_mesh_events();
34570
34712
  init_mesh_host_ownership();
34713
+ init_mesh_fast_forward();
34571
34714
 
34572
34715
  // src/mesh/preview-freshness.ts
34573
34716
  import { execFileSync as execFileSync3 } from "child_process";
@@ -37222,8 +37365,8 @@ var DaemonCommandRouter = class {
37222
37365
  }
37223
37366
  }
37224
37367
  try {
37225
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37226
- const mesh = getMesh3(meshId);
37368
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
37369
+ const mesh = getMesh2(meshId);
37227
37370
  if (mesh) return { mesh, inline: false, source: "local_config" };
37228
37371
  } catch {
37229
37372
  }
@@ -39148,8 +39291,8 @@ var DaemonCommandRouter = class {
39148
39291
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
39149
39292
  if (!meshId) return { success: false, error: "meshId required" };
39150
39293
  try {
39151
- const { deleteMesh: deleteMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39152
- const deleted = deleteMesh3(meshId);
39294
+ const { deleteMesh: deleteMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39295
+ const deleted = deleteMesh2(meshId);
39153
39296
  return { success: true, deleted };
39154
39297
  } catch (e) {
39155
39298
  return { success: false, error: e.message };
@@ -39267,7 +39410,7 @@ var DaemonCommandRouter = class {
39267
39410
  const ownerFailure = await this.requireMeshHostMutationOwner(meshId, args?.inlineMesh, "node addition");
39268
39411
  if (ownerFailure) return ownerFailure;
39269
39412
  try {
39270
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39413
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39271
39414
  const providerPriority = Array.isArray(args?.providerPriority) ? args.providerPriority.map((type) => typeof type === "string" ? type.trim() : "").filter(Boolean) : [];
39272
39415
  const readOnly = args?.readOnly === true;
39273
39416
  const policy = {
@@ -39278,7 +39421,7 @@ var DaemonCommandRouter = class {
39278
39421
  const daemonId = typeof args?.daemonId === "string" && args.daemonId.trim() ? args.daemonId.trim() : void 0;
39279
39422
  const machineId = typeof args?.machineId === "string" && args.machineId.trim() ? args.machineId.trim() : void 0;
39280
39423
  const repoRoot = typeof args?.repoRoot === "string" && args.repoRoot.trim() ? args.repoRoot.trim() : void 0;
39281
- const node = addNode3(meshId, {
39424
+ const node = addNode2(meshId, {
39282
39425
  workspace,
39283
39426
  ...repoRoot ? { repoRoot } : {},
39284
39427
  ...daemonId ? { daemonId } : {},
@@ -39469,8 +39612,8 @@ var DaemonCommandRouter = class {
39469
39612
  if (meshRecord?.inline) {
39470
39613
  removed = this.removeInlineMeshNode(meshId, mesh, nodeId);
39471
39614
  } else {
39472
- const { removeNode: removeNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39473
- removed = removeNode3(meshId, nodeId);
39615
+ const { removeNode: removeNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39616
+ removed = removeNode2(meshId, nodeId);
39474
39617
  if (removed) this.invalidateAggregateMeshStatus(meshId);
39475
39618
  }
39476
39619
  if (removed) {
@@ -39539,8 +39682,8 @@ var DaemonCommandRouter = class {
39539
39682
  };
39540
39683
  this.updateInlineMeshNode(meshId, mesh, node);
39541
39684
  } else {
39542
- const { addNode: addNode3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39543
- node = addNode3(meshId, {
39685
+ const { addNode: addNode2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39686
+ node = addNode2(meshId, {
39544
39687
  workspace: result.worktreePath,
39545
39688
  repoRoot: result.worktreePath,
39546
39689
  daemonId: sourceNode.daemonId,
@@ -39695,8 +39838,8 @@ var DaemonCommandRouter = class {
39695
39838
  mesh = args.inlineMesh;
39696
39839
  this.inlineMeshCache.set(meshId, mesh);
39697
39840
  } else {
39698
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39699
- mesh = getMesh3(meshId);
39841
+ const { getMesh: getMesh2 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
39842
+ mesh = getMesh2(meshId);
39700
39843
  }
39701
39844
  if (!mesh) return { success: false, error: "Mesh not found" };
39702
39845
  const meshHost = resolveMeshHostStatus(mesh);
@@ -49074,6 +49217,7 @@ export {
49074
49217
  buildMeshHostRequiredFailure,
49075
49218
  buildMeshLedgerReconciliationEvidence,
49076
49219
  buildMeshLedgerReplicaEvidence,
49220
+ buildMeshNodeCapabilityTags,
49077
49221
  buildP2pRelayFailurePayload,
49078
49222
  buildPinnedGlobalInstallCommand,
49079
49223
  buildRuntimeSystemChatMessage,
@@ -49201,6 +49345,7 @@ export {
49201
49345
  maybeRunDaemonUpgradeHelperFromEnv,
49202
49346
  namedKeyToAnsi,
49203
49347
  namedKeysToAnsi,
49348
+ nodeSatisfiesRequiredTags,
49204
49349
  normalizeActiveChatData,
49205
49350
  normalizeChatMessage,
49206
49351
  normalizeChatMessageKind,
@@ -49212,6 +49357,7 @@ export {
49212
49357
  normalizeInteractivePrompt,
49213
49358
  normalizeInteractivePromptResponse,
49214
49359
  normalizeManagedStatus,
49360
+ normalizeMeshCapabilityTags,
49215
49361
  normalizeMeshDaemonRole,
49216
49362
  normalizeMeshTaskMode,
49217
49363
  normalizeMeshWorkerResult,
@@ -49270,7 +49416,6 @@ export {
49270
49416
  startLocalIpcServer,
49271
49417
  suggestMeshRefineConfig,
49272
49418
  summarizeGitStatus,
49273
- syncMeshes,
49274
49419
  triggerMeshQueue,
49275
49420
  unregisterMeshCoordinator,
49276
49421
  updateConfig,