@adhdev/daemon-core 0.9.82-rc.8 → 0.9.82-rc.9

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.
@@ -42,6 +42,7 @@ export interface GitPushResult extends GitRepoIdentity {
42
42
  export interface GitCommandServices {
43
43
  getStatus?: (params: {
44
44
  workspace: string;
45
+ refreshUpstream?: boolean;
45
46
  }) => Promise<GitRepoStatus> | GitRepoStatus;
46
47
  getDiffSummary?: (params: {
47
48
  workspace: string;
@@ -5,6 +5,11 @@ export interface GitStatusOptions {
5
5
  includeSubmodules?: boolean;
6
6
  /** Optional filter to exclude specific submodule paths from status */
7
7
  submoduleIgnorePaths?: string[];
8
+ /**
9
+ * When true, refresh the tracked remote before trusting ahead/behind.
10
+ * Callers should opt into this only for convergence-critical surfaces.
11
+ */
12
+ refreshUpstream?: boolean;
8
13
  }
9
14
  export declare function getGitRepoStatus(workspace: string, options?: GitStatusOptions): Promise<GitRepoStatus>;
10
15
  interface ParsedPorcelainStatus {
@@ -27,11 +27,18 @@ export interface GitSubmoduleStatus {
27
27
  /** Error message if submodule status could not be read */
28
28
  error?: string;
29
29
  }
30
+ export type GitUpstreamFreshness = 'fresh' | 'unchecked' | 'stale' | 'no_upstream' | 'unavailable';
30
31
  export interface GitRepoStatus extends GitRepoIdentity {
31
32
  branch: string | null;
32
33
  headCommit: string | null;
33
34
  headMessage: string | null;
34
35
  upstream: string | null;
36
+ /** Whether ahead/behind was verified against a freshly fetched upstream ref. */
37
+ upstreamStatus: GitUpstreamFreshness;
38
+ /** Timestamp for the fetch that refreshed upstream refs when upstreamStatus === 'fresh'. */
39
+ upstreamFetchedAt?: number;
40
+ /** Error from the last refresh attempt when upstreamStatus === 'stale'. */
41
+ upstreamFetchError?: string;
35
42
  ahead: number;
36
43
  behind: number;
37
44
  staged: number;
@@ -105,6 +112,9 @@ export interface GitCompactSummary {
105
112
  isGitRepo: boolean;
106
113
  repoRoot: string | null;
107
114
  branch: string | null;
115
+ upstreamStatus: GitUpstreamFreshness;
116
+ upstreamFetchedAt?: number;
117
+ upstreamFetchError?: string;
108
118
  dirty: boolean;
109
119
  changedFiles: number;
110
120
  ahead: number;
package/dist/index.js CHANGED
@@ -5913,8 +5913,14 @@ async function getGitRepoStatus(workspace, options = {}) {
5913
5913
  const includeSubmodules = options.includeSubmodules !== false;
5914
5914
  try {
5915
5915
  const repo = await resolveGitRepository(workspace, options);
5916
- const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
5917
- const parsed = parsePorcelainV2Status(statusOutput.stdout);
5916
+ let parsed = await readPorcelainStatus(repo, options);
5917
+ let upstreamProbe = getInitialUpstreamProbe(parsed);
5918
+ if (options.refreshUpstream) {
5919
+ upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
5920
+ if (upstreamProbe.upstreamStatus === "fresh") {
5921
+ parsed = await readPorcelainStatus(repo, options);
5922
+ }
5923
+ }
5918
5924
  const head = await readHead(repo, options);
5919
5925
  const stashCount = await readStashCount(repo, options);
5920
5926
  let submodules;
@@ -5929,6 +5935,9 @@ async function getGitRepoStatus(workspace, options = {}) {
5929
5935
  headCommit: head.commit,
5930
5936
  headMessage: head.message,
5931
5937
  upstream: parsed.upstream,
5938
+ upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
5939
+ upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
5940
+ upstreamFetchError: upstreamProbe.upstreamFetchError,
5932
5941
  ahead: parsed.ahead,
5933
5942
  behind: parsed.behind,
5934
5943
  staged: parsed.staged,
@@ -5953,6 +5962,60 @@ async function getGitRepoStatus(workspace, options = {}) {
5953
5962
  );
5954
5963
  }
5955
5964
  }
5965
+ async function readPorcelainStatus(repo, options) {
5966
+ const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
5967
+ return parsePorcelainV2Status(statusOutput.stdout);
5968
+ }
5969
+ function getInitialUpstreamProbe(parsed) {
5970
+ return {
5971
+ upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
5972
+ };
5973
+ }
5974
+ async function refreshTrackedUpstream(repo, parsed, options) {
5975
+ if (!parsed.upstream || !parsed.branch) {
5976
+ return { upstreamStatus: "no_upstream" };
5977
+ }
5978
+ const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
5979
+ if (!remoteName) {
5980
+ return {
5981
+ upstreamStatus: "stale",
5982
+ upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
5983
+ };
5984
+ }
5985
+ try {
5986
+ await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
5987
+ return {
5988
+ upstreamStatus: "fresh",
5989
+ upstreamFetchedAt: Date.now()
5990
+ };
5991
+ } catch (error) {
5992
+ return {
5993
+ upstreamStatus: "stale",
5994
+ upstreamFetchError: formatGitError(error)
5995
+ };
5996
+ }
5997
+ }
5998
+ async function readBranchRemote(repo, branch, options) {
5999
+ try {
6000
+ const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
6001
+ return result.stdout.trim() || null;
6002
+ } catch {
6003
+ return null;
6004
+ }
6005
+ }
6006
+ function inferRemoteName(upstream) {
6007
+ const [remoteName] = upstream.split("/");
6008
+ return remoteName?.trim() || null;
6009
+ }
6010
+ function formatGitError(error) {
6011
+ if (error instanceof GitCommandError) {
6012
+ return error.stderr || error.message;
6013
+ }
6014
+ if (error instanceof Error) {
6015
+ return error.message;
6016
+ }
6017
+ return String(error);
6018
+ }
5956
6019
  function parsePorcelainV2Status(output) {
5957
6020
  const parsed = {
5958
6021
  branch: null,
@@ -6047,6 +6110,7 @@ function emptyStatus(workspace, lastCheckedAt, error) {
6047
6110
  headCommit: null,
6048
6111
  headMessage: null,
6049
6112
  upstream: null,
6113
+ upstreamStatus: "unavailable",
6050
6114
  ahead: 0,
6051
6115
  behind: 0,
6052
6116
  staged: 0,
@@ -6327,6 +6391,9 @@ function createGitCompactSummary(status, diffSummary) {
6327
6391
  isGitRepo: status.isGitRepo,
6328
6392
  repoRoot: status.repoRoot,
6329
6393
  branch: status.branch,
6394
+ upstreamStatus: status.upstreamStatus,
6395
+ upstreamFetchedAt: status.upstreamFetchedAt,
6396
+ upstreamFetchError: status.upstreamFetchError,
6330
6397
  dirty: status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0 || conflictCount > 0 || changedFiles > 0,
6331
6398
  changedFiles,
6332
6399
  ahead: status.ahead,
@@ -6671,7 +6738,7 @@ var defaultSnapshotStore = createGitSnapshotStore({
6671
6738
  });
6672
6739
  function createDefaultGitCommandServices() {
6673
6740
  return {
6674
- getStatus: ({ workspace }) => getGitRepoStatus(workspace),
6741
+ getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
6675
6742
  getDiffSummary: ({ workspace }) => getGitDiffSummary(workspace),
6676
6743
  getDiffFile: ({ workspace, path: filePath }) => getGitFileDiff(workspace, filePath),
6677
6744
  createSnapshot: ({ workspace, reason, sessionId, turnId }) => defaultSnapshotStore.create({
@@ -6757,7 +6824,7 @@ async function handleGitCommand(command, args, services = defaultGitCommandServi
6757
6824
  switch (command) {
6758
6825
  case "git_status": {
6759
6826
  if (!services.getStatus) return serviceNotImplemented(command);
6760
- const status = await runService(() => services.getStatus({ workspace }));
6827
+ const status = await runService(() => services.getStatus({ workspace, refreshUpstream: optionalBoolean(args?.refreshUpstream) }));
6761
6828
  return "success" in status ? status : { success: true, status };
6762
6829
  }
6763
6830
  case "git_diff_summary": {
@@ -26365,7 +26432,7 @@ ${block}`);
26365
26432
  continue;
26366
26433
  }
26367
26434
  try {
26368
- const gitStatus = await getGitRepoStatus(node.workspace, { timeoutMs: 1e4 });
26435
+ const gitStatus = await getGitRepoStatus(node.workspace, { timeoutMs: 1e4, refreshUpstream: true });
26369
26436
  status.git = gitStatus;
26370
26437
  if (gitStatus.isGitRepo) {
26371
26438
  status.health = deriveMeshNodeHealthFromGit(gitStatus);