@adhdev/daemon-core 0.9.82-rc.62 → 0.9.82-rc.64

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.62",
3
+ "version": "0.9.82-rc.64",
4
4
  "description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -249,6 +249,10 @@ function normalizeInlineMeshGitStatus(
249
249
  headCommit: readStringValue(status.headCommit) ?? null,
250
250
  headMessage: readStringValue(status.headMessage) ?? null,
251
251
  upstream: readStringValue(status.upstream) ?? null,
252
+ upstreamStatus: readStringValue(status.upstreamStatus, status.upstream_status)
253
+ ?? (readStringValue(status.upstream) ? 'unchecked' : 'no_upstream'),
254
+ upstreamFetchedAt: readNumberValue(status.upstreamFetchedAt, status.upstream_fetched_at),
255
+ upstreamFetchError: readStringValue(status.upstreamFetchError, status.upstream_fetch_error),
252
256
  ahead: readNumberValue(status.ahead) ?? 0,
253
257
  behind: readNumberValue(status.behind) ?? 0,
254
258
  staged: readNumberValue(status.staged) ?? 0,
@@ -490,8 +494,16 @@ function reconcileInlineMeshCache(cached: any, incoming: any): any {
490
494
  }
491
495
 
492
496
  function hasGitWorktreeChanges(git: Record<string, unknown> | null | undefined): boolean {
493
- if (!git) return false;
494
- return Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
497
+ return countGitWorktreeChanges(git) > 0;
498
+ }
499
+
500
+ function countGitWorktreeChanges(git: Record<string, unknown> | null | undefined): number {
501
+ if (!git) return 0;
502
+ return Number(git.staged || 0)
503
+ + Number(git.modified || 0)
504
+ + Number(git.untracked || 0)
505
+ + Number(git.deleted || 0)
506
+ + Number(git.renamed || 0);
495
507
  }
496
508
 
497
509
  function getGitSubmoduleDriftState(git: Record<string, unknown> | null | undefined): { dirty: boolean; outOfSync: boolean } {
@@ -516,6 +528,167 @@ function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undef
516
528
  return 'online';
517
529
  }
518
530
 
531
+ function readMeshNodeLabel(status: Record<string, unknown>, node: any): string {
532
+ return readStringValue(status.nodeId, node?.id, node?.nodeId) ?? 'unknown';
533
+ }
534
+
535
+ function buildInlineMeshBranchConvergence(args: {
536
+ mesh: any;
537
+ node: any;
538
+ status: Record<string, unknown>;
539
+ }): Record<string, unknown> {
540
+ const git = readObjectRecord(args.status.git);
541
+ const nodeLabel = readMeshNodeLabel(args.status, args.node);
542
+ const defaultBranch = readStringValue(args.mesh?.defaultBranch) ?? 'main';
543
+ const branch = readStringValue(git.branch, args.node?.worktreeBranch) ?? null;
544
+ const upstream = readStringValue(git.upstream) ?? null;
545
+ const upstreamStatus = readStringValue(git.upstreamStatus, git.upstream_status)
546
+ ?? (upstream ? 'unchecked' : 'no_upstream');
547
+ const ahead = readNumberValue(git.ahead) ?? 0;
548
+ const behind = readNumberValue(git.behind) ?? 0;
549
+ const uncommittedChanges = countGitWorktreeChanges(git);
550
+ const hasConflicts = readBooleanValue(git.hasConflicts)
551
+ ?? (Array.isArray(git.conflictFiles) && git.conflictFiles.length > 0);
552
+ const base = {
553
+ defaultBranch,
554
+ branch,
555
+ upstream,
556
+ upstreamStatus,
557
+ ahead,
558
+ behind,
559
+ isWorktree: args.node?.isLocalWorktree === true || args.status.isLocalWorktree === true,
560
+ isDefaultBranch: branch === defaultBranch,
561
+ };
562
+
563
+ if (readBooleanValue(git.isGitRepo) !== true) {
564
+ return {
565
+ ...base,
566
+ status: 'blocked_review',
567
+ needsConvergence: true,
568
+ reason: 'git_status_unavailable',
569
+ nextStep: `Resolve git status for node '${nodeLabel}' before marking the task complete.`,
570
+ };
571
+ }
572
+
573
+ if (!branch) {
574
+ return {
575
+ ...base,
576
+ status: 'blocked_review',
577
+ needsConvergence: true,
578
+ reason: 'branch_unknown',
579
+ nextStep: `Inspect node '${nodeLabel}' git branch before deciding whether it is merged to ${defaultBranch}.`,
580
+ };
581
+ }
582
+
583
+ if (hasConflicts || uncommittedChanges > 0) {
584
+ return {
585
+ ...base,
586
+ status: 'not_mergeable',
587
+ needsConvergence: true,
588
+ reason: hasConflicts ? 'conflicts_present' : 'dirty_workspace',
589
+ nextStep: `Commit, checkpoint, or resolve node '${nodeLabel}' before any main convergence step.`,
590
+ };
591
+ }
592
+
593
+ if (branch === defaultBranch) {
594
+ if (upstream && upstreamStatus !== 'fresh') {
595
+ return {
596
+ ...base,
597
+ status: 'blocked_review',
598
+ needsConvergence: true,
599
+ reason: 'default_branch_upstream_unverified',
600
+ nextStep: `Refresh ${defaultBranch}'s upstream refs or resolve the fetch failure before declaring convergence complete for node '${nodeLabel}'.`,
601
+ };
602
+ }
603
+ if (ahead > 0 || behind > 0) {
604
+ return {
605
+ ...base,
606
+ status: 'blocked_review',
607
+ needsConvergence: true,
608
+ reason: 'default_branch_not_even_with_upstream',
609
+ nextStep: `Bring ${defaultBranch} even with its upstream before declaring convergence complete.`,
610
+ };
611
+ }
612
+ return {
613
+ ...base,
614
+ status: 'merged_to_main',
615
+ needsConvergence: false,
616
+ reason: 'clean_default_branch',
617
+ nextStep: null,
618
+ };
619
+ }
620
+
621
+ if (args.node?.isLocalWorktree === true || args.status.isLocalWorktree === true) {
622
+ return {
623
+ ...base,
624
+ status: 'cleanup_candidate',
625
+ needsConvergence: true,
626
+ reason: 'clean_non_default_worktree_branch',
627
+ nextStep: `Run mesh_refine_node(node_id: "${nodeLabel}") or explicitly classify this worktree as blocked_review/not_mergeable before ending the task.`,
628
+ };
629
+ }
630
+
631
+ if (upstream && upstreamStatus !== 'fresh') {
632
+ return {
633
+ ...base,
634
+ status: 'blocked_review',
635
+ needsConvergence: true,
636
+ reason: 'feature_branch_upstream_unverified',
637
+ nextStep: `Refresh branch '${branch}' upstream refs or resolve the fetch failure before deciding whether it is ready to merge into ${defaultBranch}.`,
638
+ };
639
+ }
640
+
641
+ if (!upstream || ahead > 0 || behind > 0) {
642
+ return {
643
+ ...base,
644
+ status: 'blocked_review',
645
+ needsConvergence: true,
646
+ reason: !upstream ? 'feature_branch_missing_upstream' : 'feature_branch_not_even_with_upstream',
647
+ nextStep: `Push or reconcile branch '${branch}', then merge it into ${defaultBranch} or mark it not_mergeable with a reason.`,
648
+ };
649
+ }
650
+
651
+ return {
652
+ ...base,
653
+ status: 'pushed_feature_branch_needs_merge',
654
+ needsConvergence: true,
655
+ reason: 'clean_non_default_branch',
656
+ nextStep: `Review and merge branch '${branch}' into ${defaultBranch}; do not report the task as fully complete while it remains off main.`,
657
+ };
658
+ }
659
+
660
+ function applyInlineMeshBranchConvergence(mesh: any, node: any, status: Record<string, unknown>): void {
661
+ const git = readObjectRecord(status.git);
662
+ if (Object.keys(git).length === 0 && !status.gitProbePending) return;
663
+ const uncommittedChanges = countGitWorktreeChanges(git);
664
+ status.isDirty = uncommittedChanges > 0;
665
+ status.uncommittedChanges = uncommittedChanges;
666
+ status.branchConvergence = buildInlineMeshBranchConvergence({ mesh, node, status });
667
+ }
668
+
669
+ function summarizeInlineMeshBranchConvergence(nodes: Array<Record<string, unknown>>): Record<string, unknown> {
670
+ const followUps = nodes
671
+ .filter(node => readObjectRecord(node.branchConvergence).needsConvergence === true)
672
+ .map(node => {
673
+ const convergence = readObjectRecord(node.branchConvergence);
674
+ return {
675
+ nodeId: node.nodeId,
676
+ workspace: node.workspace,
677
+ branch: convergence.branch,
678
+ status: convergence.status,
679
+ reason: convergence.reason,
680
+ nextStep: convergence.nextStep,
681
+ };
682
+ });
683
+
684
+ return {
685
+ needsFollowUp: followUps.length > 0,
686
+ unresolvedCount: followUps.length,
687
+ requiredFinalStates: ['merged_to_main', 'pushed_feature_branch_needs_merge', 'blocked_review', 'cleanup_candidate', 'not_mergeable'],
688
+ followUps,
689
+ };
690
+ }
691
+
519
692
  function readCachedInlineMeshActiveSessions(node: any): string[] {
520
693
  const cachedStatus = readObjectRecord(node?.cachedStatus);
521
694
  const activeSession = readObjectRecord(cachedStatus.activeSession);
@@ -1466,6 +1639,7 @@ export class DaemonCommandRouter {
1466
1639
  const nextStatus = { ...statusNode };
1467
1640
  nextStatus.git = liveGit;
1468
1641
  nextStatus.health = deriveMeshNodeHealthFromGit(liveGit);
1642
+ applyInlineMeshBranchConvergence(mesh, inlineNode, nextStatus);
1469
1643
  nextStatus.launchReady = readBooleanValue(nextStatus.launchReady) ?? true;
1470
1644
  const connection = readObjectRecord(nextStatus.connection);
1471
1645
  const connectionState = readStringValue(connection.state);
@@ -1505,6 +1679,7 @@ export class DaemonCommandRouter {
1505
1679
  error: 'Selected coordinator could not confirm direct mesh truth for every remote node yet.',
1506
1680
  } : {}),
1507
1681
  sourceOfTruth: nextSourceOfTruth,
1682
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodes),
1508
1683
  nodes,
1509
1684
  };
1510
1685
  }
@@ -4639,11 +4814,13 @@ export class DaemonCommandRouter {
4639
4814
  node,
4640
4815
  pendingPeerGitProbe ? { skipGit: true, skipError: true, skipHealth: true } : undefined,
4641
4816
  )) {
4817
+ applyInlineMeshBranchConvergence(mesh, node, status);
4642
4818
  finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
4643
4819
  nodeStatuses.push(status);
4644
4820
  continue;
4645
4821
  }
4646
4822
  if (meshRecord?.source === 'inline_cache' && !isSelfNode) {
4823
+ applyInlineMeshBranchConvergence(mesh, node, status);
4647
4824
  finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
4648
4825
  nodeStatuses.push(status);
4649
4826
  continue;
@@ -4669,6 +4846,7 @@ export class DaemonCommandRouter {
4669
4846
  } else {
4670
4847
  applyCachedInlineMeshNodeStatus(status, node);
4671
4848
  }
4849
+ applyInlineMeshBranchConvergence(mesh, node, status);
4672
4850
  finalizeMeshNodeStatus({ status, node, daemonId, isSelfNode });
4673
4851
  nodeStatuses.push(status);
4674
4852
  }
@@ -4709,6 +4887,7 @@ export class DaemonCommandRouter {
4709
4887
  } : {}),
4710
4888
  historicalEvidenceOnly: ['recoveryHints', 'ledger.summary', 'queue.summary'],
4711
4889
  },
4890
+ branchConvergenceSummary: summarizeInlineMeshBranchConvergence(nodeStatuses),
4712
4891
  nodes: nodeStatuses,
4713
4892
  queue: { tasks: queue, summary: queueSummary },
4714
4893
  ledger: { entries: ledgerEntries, summary: ledgerSummary },