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