@adhdev/daemon-core 0.9.82-rc.2 → 0.9.82-rc.200

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.
Files changed (244) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +3 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/chat/source-machine.d.ts +166 -0
  4. package/dist/chat/source-resolver.d.ts +104 -0
  5. package/dist/chat/subscription-updates.d.ts +1 -0
  6. package/dist/cli-adapter-types.d.ts +23 -2
  7. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  8. package/dist/cli-adapters/cli-state-engine.d.ts +178 -0
  9. package/dist/cli-adapters/provider-cli-adapter.d.ts +106 -63
  10. package/dist/cli-adapters/provider-cli-parse.d.ts +4 -0
  11. package/dist/cli-adapters/provider-cli-shared.d.ts +29 -0
  12. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  13. package/dist/commands/cli-manager.d.ts +2 -1
  14. package/dist/commands/handler.d.ts +110 -0
  15. package/dist/commands/mesh-coordinator.d.ts +85 -1
  16. package/dist/commands/router.d.ts +29 -1
  17. package/dist/config/chat-history.d.ts +9 -0
  18. package/dist/config/config.d.ts +5 -0
  19. package/dist/config/mesh-config.d.ts +72 -1
  20. package/dist/git/git-commands.d.ts +8 -1
  21. package/dist/git/git-status.d.ts +5 -0
  22. package/dist/git/git-types.d.ts +12 -0
  23. package/dist/index.d.ts +39 -10
  24. package/dist/index.js +29425 -12026
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +25700 -8378
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/installer.d.ts +1 -4
  29. package/dist/ipc/local-ipc-server.d.ts +91 -0
  30. package/dist/launch.d.ts +1 -1
  31. package/dist/logging/async-batch-writer.d.ts +10 -0
  32. package/dist/mesh/contracts.d.ts +164 -0
  33. package/dist/mesh/coordinator-prompt.d.ts +30 -0
  34. package/dist/mesh/coordinator-registry.d.ts +59 -0
  35. package/dist/mesh/mesh-active-work.d.ts +90 -0
  36. package/dist/mesh/mesh-delivery-policy.d.ts +126 -0
  37. package/dist/mesh/mesh-events.d.ts +167 -6
  38. package/dist/mesh/mesh-fast-forward.d.ts +41 -0
  39. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  40. package/dist/mesh/mesh-ledger.d.ts +58 -1
  41. package/dist/mesh/mesh-refine-status.d.ts +26 -0
  42. package/dist/mesh/mesh-runtime-store.d.ts +149 -0
  43. package/dist/mesh/mesh-work-queue.d.ts +55 -6
  44. package/dist/mesh/preview-freshness.d.ts +18 -0
  45. package/dist/mesh/refine-config.d.ts +193 -0
  46. package/dist/mesh/worktree-bootstrap-config.d.ts +113 -0
  47. package/dist/providers/approval-utils.d.ts +13 -0
  48. package/dist/providers/chat-message-normalization.d.ts +1 -0
  49. package/dist/providers/cli-provider-instance.d.ts +39 -3
  50. package/dist/providers/contracts.d.ts +130 -6
  51. package/dist/providers/external-sources.d.ts +71 -0
  52. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  53. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  54. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  55. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  56. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  57. package/dist/providers/native-history/index.d.ts +13 -0
  58. package/dist/providers/provider-instance-manager.d.ts +12 -0
  59. package/dist/providers/provider-instance.d.ts +13 -1
  60. package/dist/providers/provider-loader.d.ts +26 -4
  61. package/dist/providers/provider-trust.d.ts +31 -0
  62. package/dist/providers/read-chat-contract.d.ts +29 -0
  63. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  64. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +85 -0
  65. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  66. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  67. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  68. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  69. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  70. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  71. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  72. package/dist/providers/sdk/v1/index.d.ts +30 -0
  73. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  74. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  75. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  76. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  77. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  78. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  79. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  80. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  81. package/dist/providers/spec/adapter.d.ts +60 -0
  82. package/dist/providers/spec/cli-adapter.d.ts +98 -0
  83. package/dist/providers/spec/driver.d.ts +190 -0
  84. package/dist/providers/spec/evaluator.d.ts +55 -0
  85. package/dist/providers/spec/loader.d.ts +14 -0
  86. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  87. package/dist/providers/spec/route.d.ts +4 -0
  88. package/dist/providers/spec/schema.gen.d.ts +599 -0
  89. package/dist/providers/spec/types.d.ts +282 -0
  90. package/dist/providers/transcript-v2.d.ts +176 -0
  91. package/dist/providers/types/interactive-prompt.d.ts +48 -0
  92. package/dist/repo-mesh-types.d.ts +230 -1
  93. package/dist/sessions/registry.d.ts +3 -0
  94. package/dist/shared-types-extra.d.ts +1 -1
  95. package/dist/shared-types.d.ts +42 -1
  96. package/dist/status/normalize.d.ts +1 -1
  97. package/dist/status/normalize.js +1 -0
  98. package/dist/status/normalize.js.map +1 -1
  99. package/dist/status/normalize.mjs +1 -0
  100. package/dist/status/normalize.mjs.map +1 -1
  101. package/dist/status/reporter.d.ts +2 -0
  102. package/dist/status/snapshot.d.ts +1 -0
  103. package/dist/types.d.ts +5 -0
  104. package/package.json +7 -2
  105. package/src/agent-stream/poller.ts +2 -3
  106. package/src/boot/daemon-lifecycle.ts +22 -10
  107. package/src/boot/process-hardening.ts +89 -0
  108. package/src/chat/source-machine.ts +534 -0
  109. package/src/chat/source-resolver.ts +0 -0
  110. package/src/chat/subscription-updates.ts +20 -1
  111. package/src/cli-adapter-types.d.ts +1 -0
  112. package/src/cli-adapter-types.ts +22 -2
  113. package/src/cli-adapters/cli-script-runner.ts +421 -0
  114. package/src/cli-adapters/cli-state-engine.ts +1138 -0
  115. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  116. package/src/cli-adapters/provider-cli-adapter.ts +670 -1137
  117. package/src/cli-adapters/provider-cli-parse.d.ts +1 -0
  118. package/src/cli-adapters/provider-cli-parse.ts +13 -0
  119. package/src/cli-adapters/provider-cli-runtime.ts +3 -1
  120. package/src/cli-adapters/provider-cli-shared.d.ts +2 -0
  121. package/src/cli-adapters/provider-cli-shared.ts +59 -11
  122. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  123. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +17 -1
  124. package/src/cli-adapters/terminal-backends/xterm-backend.ts +8 -1
  125. package/src/commands/chat-commands.ts +1787 -60
  126. package/src/commands/cli-manager.ts +283 -14
  127. package/src/commands/handler.ts +809 -2
  128. package/src/commands/mesh-coordinator.ts +331 -122
  129. package/src/commands/router.ts +3915 -546
  130. package/src/config/chat-history.ts +95 -24
  131. package/src/config/config.ts +12 -0
  132. package/src/config/mesh-config.ts +280 -2
  133. package/src/config/recent-activity.ts +8 -2
  134. package/src/daemon/dev-cli-debug.ts +10 -1
  135. package/src/detection/ide-detector.ts +26 -16
  136. package/src/git/git-commands.ts +38 -8
  137. package/src/git/git-status.ts +132 -12
  138. package/src/git/git-summary.ts +3 -0
  139. package/src/git/git-types.ts +13 -0
  140. package/src/git/git-worktree.ts +8 -1
  141. package/src/index.ts +127 -9
  142. package/src/installer.d.ts +1 -1
  143. package/src/installer.ts +8 -6
  144. package/src/ipc/local-ipc-server.ts +278 -0
  145. package/src/launch.d.ts +1 -1
  146. package/src/launch.ts +37 -28
  147. package/src/logging/async-batch-writer.ts +55 -0
  148. package/src/logging/logger.ts +2 -1
  149. package/src/mesh/contracts.ts +329 -0
  150. package/src/mesh/coordinator-prompt.ts +204 -30
  151. package/src/mesh/coordinator-registry.ts +121 -0
  152. package/src/mesh/mesh-active-work.ts +437 -0
  153. package/src/mesh/mesh-delivery-policy.ts +298 -0
  154. package/src/mesh/mesh-events.ts +1352 -109
  155. package/src/mesh/mesh-fast-forward.ts +438 -0
  156. package/src/mesh/mesh-host-ownership.ts +73 -0
  157. package/src/mesh/mesh-ledger.ts +457 -104
  158. package/src/mesh/mesh-refine-status.ts +144 -0
  159. package/src/mesh/mesh-runtime-store.ts +769 -0
  160. package/src/mesh/mesh-work-queue.ts +267 -159
  161. package/src/mesh/preview-freshness.ts +118 -0
  162. package/src/mesh/refine-config.ts +366 -0
  163. package/src/mesh/worktree-bootstrap-config.ts +247 -0
  164. package/src/providers/approval-utils.d.ts +4 -0
  165. package/src/providers/approval-utils.ts +47 -5
  166. package/src/providers/chat-message-normalization.ts +7 -12
  167. package/src/providers/cli-provider-instance.ts +806 -64
  168. package/src/providers/contracts.d.ts +55 -0
  169. package/src/providers/contracts.ts +141 -6
  170. package/src/providers/external-sources.ts +218 -0
  171. package/src/providers/ide-provider-instance.ts +19 -5
  172. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  173. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  174. package/src/providers/native-history/codex-cli-transcript.ts +419 -0
  175. package/src/providers/native-history/dispatcher.ts +340 -0
  176. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  177. package/src/providers/native-history/index.ts +30 -0
  178. package/src/providers/provider-instance-manager.ts +30 -0
  179. package/src/providers/provider-instance.ts +10 -1
  180. package/src/providers/provider-loader.ts +582 -50
  181. package/src/providers/provider-schema.ts +87 -14
  182. package/src/providers/provider-trust.ts +114 -0
  183. package/src/providers/read-chat-contract.ts +76 -16
  184. package/src/providers/sdk/README.md +49 -0
  185. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  186. package/src/providers/sdk/v1/builders/cli/detect-status.ts +262 -0
  187. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  188. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  189. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  190. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  191. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  192. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  193. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  194. package/src/providers/sdk/v1/index.ts +152 -0
  195. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  196. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  197. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  198. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  199. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  200. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  201. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  202. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  203. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  204. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  205. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  206. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  207. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  208. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  209. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  210. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  211. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  212. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  213. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  214. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  215. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  216. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  217. package/src/providers/sdk/v1/validators/index.ts +19 -0
  218. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  219. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  220. package/src/providers/spec/adapter.ts +176 -0
  221. package/src/providers/spec/cli-adapter.ts +538 -0
  222. package/src/providers/spec/driver.ts +725 -0
  223. package/src/providers/spec/evaluator.ts +373 -0
  224. package/src/providers/spec/loader.ts +130 -0
  225. package/src/providers/spec/native-history-executor.ts +939 -0
  226. package/src/providers/spec/route.ts +51 -0
  227. package/src/providers/spec/schema.gen.ts +559 -0
  228. package/src/providers/spec/schema.json +237 -0
  229. package/src/providers/spec/types.ts +301 -0
  230. package/src/providers/transcript-v2.ts +567 -0
  231. package/src/providers/types/interactive-prompt.ts +425 -0
  232. package/src/providers/version-archive.ts +38 -20
  233. package/src/repo-mesh-types.ts +250 -1
  234. package/src/sessions/registry.ts +3 -0
  235. package/src/shared-types-extra.ts +1 -1
  236. package/src/shared-types.ts +45 -1
  237. package/src/status/builders.ts +24 -6
  238. package/src/status/normalize.ts +2 -0
  239. package/src/status/reporter.ts +15 -0
  240. package/src/status/snapshot.ts +84 -25
  241. package/src/system/host-memory.ts +29 -12
  242. package/src/types.ts +5 -0
  243. package/dist/mesh/mesh-sync.d.ts +0 -53
  244. package/src/mesh/mesh-sync.ts +0 -111
@@ -7,8 +7,10 @@
7
7
  * Migrated from @adhdev/core — this is now the single source of truth.
8
8
  */
9
9
 
10
- import { execSync } from 'child_process';
11
- import { existsSync } from 'fs';
10
+ import { exec } from 'child_process';
11
+ import { promisify } from 'util';
12
+ const execAsync = promisify(exec);
13
+ import { existsSync, statSync } from 'fs';
12
14
  import { platform, homedir } from 'os';
13
15
  import * as path from 'path';
14
16
  import type { ProviderLoader } from '../providers/provider-loader.js';
@@ -73,25 +75,33 @@ function findCliCommand(command: string): string | null {
73
75
  const resolved = path.isAbsolute(candidate) ? candidate : path.resolve(candidate);
74
76
  return existsSync(resolved) ? resolved : null;
75
77
  }
76
- try {
77
- const result = execSync(
78
- platform() === 'win32' ? `where ${trimmed}` : `which ${trimmed}`,
79
- { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }
80
- ).trim();
81
- return result.split('\n')[0] || null;
82
- } catch {
83
- return null;
78
+ const isWin = platform() === 'win32';
79
+ const paths = (process.env.PATH || '').split(isWin ? ';' : ':');
80
+ const exes = isWin ? ['.exe', '.cmd', '.bat', ''] : [''];
81
+ for (const p of paths) {
82
+ if (!p) continue;
83
+ for (const ext of exes) {
84
+ const fullPath = path.join(p, trimmed + ext);
85
+ try {
86
+ if (existsSync(fullPath)) {
87
+ const stat = statSync(fullPath);
88
+ if (stat.isFile() && (isWin || (stat.mode & 0o111))) {
89
+ return fullPath;
90
+ }
91
+ }
92
+ } catch { }
93
+ }
84
94
  }
95
+ return null;
85
96
  }
86
97
 
87
- function getIdeVersion(cliCommand: string): string | null {
98
+ async function getIdeVersion(cliCommand: string): Promise<string | null> {
88
99
  try {
89
- const result = execSync(`"${cliCommand}" --version`, {
100
+ const { stdout } = await execAsync(`"${cliCommand}" --version`, {
90
101
  encoding: 'utf-8',
91
102
  timeout: 10000,
92
- stdio: ['pipe', 'pipe', 'pipe'],
93
- }).trim();
94
- return result.split('\n')[0] || null;
103
+ });
104
+ return stdout.trim().split('\n')[0] || null;
95
105
  } catch {
96
106
  return null;
97
107
  }
@@ -152,7 +162,7 @@ export async function detectIDEs(providerLoader?: ProviderLoader): Promise<IDEIn
152
162
  const installed = os === 'darwin'
153
163
  ? !!(resolvedCli || appPath)
154
164
  : !!resolvedCli;
155
- const version = resolvedCli ? getIdeVersion(resolvedCli) : null;
165
+ const version = resolvedCli ? await getIdeVersion(resolvedCli) : null;
156
166
 
157
167
  results.push({
158
168
  id: def.id,
@@ -42,8 +42,12 @@ export interface GitLogResult extends GitRepoIdentity {
42
42
  }
43
43
 
44
44
  export interface GitCheckpointResult extends GitRepoIdentity {
45
- commit: string;
45
+ commit?: string;
46
46
  message: string;
47
+ status?: 'created' | 'skipped';
48
+ skipped?: boolean;
49
+ noop?: boolean;
50
+ reason?: 'nothing_to_commit';
47
51
  lastCheckedAt: number;
48
52
  }
49
53
 
@@ -62,7 +66,7 @@ export interface GitPushResult extends GitRepoIdentity {
62
66
  }
63
67
 
64
68
  export interface GitCommandServices {
65
- getStatus?: (params: { workspace: string }) => Promise<GitRepoStatus> | GitRepoStatus;
69
+ getStatus?: (params: { workspace: string; refreshUpstream?: boolean; includeSubmodules?: boolean; submoduleIgnorePaths?: string[] }) => Promise<GitRepoStatus> | GitRepoStatus;
66
70
  getDiffSummary?: (params: { workspace: string; staged?: boolean }) => Promise<GitDiffSummary> | GitDiffSummary;
67
71
  getDiffFile?: (params: { workspace: string; path: string; staged?: boolean }) => Promise<GitFileDiff> | GitFileDiff;
68
72
  createSnapshot?: (params: {
@@ -171,7 +175,7 @@ const defaultSnapshotStore = createGitSnapshotStore({
171
175
 
172
176
  export function createDefaultGitCommandServices(): GitCommandServices {
173
177
  return {
174
- getStatus: ({ workspace }) => getGitRepoStatus(workspace),
178
+ getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
175
179
  getDiffSummary: ({ workspace }) => getGitDiffSummary(workspace),
176
180
  getDiffFile: ({ workspace, path: filePath }) => getGitFileDiff(workspace, filePath),
177
181
  createSnapshot: ({ workspace, reason, sessionId, turnId }) => defaultSnapshotStore.create({
@@ -290,7 +294,16 @@ export async function handleGitCommand(
290
294
  switch (command) {
291
295
  case 'git_status': {
292
296
  if (!services.getStatus) return serviceNotImplemented(command);
293
- const status = await runService(() => services.getStatus!({ workspace }));
297
+ const submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths)
298
+ ? args.submoduleIgnorePaths.filter((value: unknown): value is string => typeof value === 'string' && value.trim().length > 0)
299
+ : undefined;
300
+ const statusParams: { workspace: string; refreshUpstream?: boolean; includeSubmodules?: boolean; submoduleIgnorePaths?: string[] } = { workspace };
301
+ const refreshUpstream = optionalBoolean(args?.refreshUpstream);
302
+ const includeSubmodules = optionalBoolean(args?.includeSubmodules);
303
+ if (refreshUpstream !== undefined) statusParams.refreshUpstream = refreshUpstream;
304
+ if (includeSubmodules !== undefined) statusParams.includeSubmodules = includeSubmodules;
305
+ if (submoduleIgnorePaths && submoduleIgnorePaths.length > 0) statusParams.submoduleIgnorePaths = submoduleIgnorePaths;
306
+ const status = await runService(() => services.getStatus!(statusParams));
294
307
  return 'success' in status ? status : { success: true, status };
295
308
  }
296
309
 
@@ -442,6 +455,15 @@ async function gitCheckpoint(
442
455
  if (statusResult.hasConflicts) {
443
456
  throw new GitCommandError('conflict', 'Repository has conflicts — resolve before checkpointing');
444
457
  }
458
+ const dirtySubmodules = (statusResult.submodules || []).filter(submodule => submodule.dirty);
459
+ if (dirtySubmodules.length > 0) {
460
+ const paths = dirtySubmodules.map(submodule => submodule.path).join(', ');
461
+ throw new GitCommandError(
462
+ 'dirty_index_required',
463
+ `Repository has dirty submodules that must be checkpointed first: ${paths}. ` +
464
+ 'Checkpoint or commit each dirty submodule, then checkpoint this repository to record gitlink changes.',
465
+ );
466
+ }
445
467
 
446
468
  const addArgs = includeUntracked ? ['-A'] : ['-u'];
447
469
  await runGit(repo, ['add', ...addArgs], { cwd: repoRoot });
@@ -455,10 +477,17 @@ async function gitCheckpoint(
455
477
  } catch (err: any) {
456
478
  const output = (err?.stdout || '') + (err?.stderr || '');
457
479
  if (/nothing to commit/i.test(output)) {
458
- throw new GitCommandError('nothing_to_commit', 'Nothing to commit — working tree is clean.', {
459
- stdout: err?.stdout,
460
- stderr: err?.stderr,
461
- });
480
+ return {
481
+ workspace: repo.workspace,
482
+ repoRoot,
483
+ isGitRepo: true,
484
+ message: fullMsg,
485
+ status: 'skipped',
486
+ skipped: true,
487
+ noop: true,
488
+ reason: 'nothing_to_commit',
489
+ lastCheckedAt: Date.now(),
490
+ };
462
491
  }
463
492
  throw err;
464
493
  }
@@ -469,6 +498,7 @@ async function gitCheckpoint(
469
498
  isGitRepo: true,
470
499
  commit: commitSha,
471
500
  message: fullMsg,
501
+ status: 'created',
472
502
  lastCheckedAt: Date.now(),
473
503
  };
474
504
  }
@@ -1,12 +1,25 @@
1
- import type { GitRepoStatus, GitSubmoduleStatus } from './git-types.js';
1
+ import type { GitRepoStatus, GitSubmoduleStatus, GitUpstreamFreshness } from './git-types.js';
2
2
  import { GitCommandError, resolveGitRepository, runGit } from './git-executor.js';
3
3
 
4
+ type ResolvedGitRepo = { workspace: string; repoRoot: string | null; isGitRepo: boolean };
5
+
4
6
  export interface GitStatusOptions {
5
7
  timeoutMs?: number;
6
8
  /** When true, include submodule status in the result. Defaults to true. */
7
9
  includeSubmodules?: boolean;
8
10
  /** Optional filter to exclude specific submodule paths from status */
9
11
  submoduleIgnorePaths?: string[];
12
+ /**
13
+ * When true, refresh the tracked remote before trusting ahead/behind.
14
+ * Callers should opt into this only for convergence-critical surfaces.
15
+ */
16
+ refreshUpstream?: boolean;
17
+ }
18
+
19
+ interface GitUpstreamProbe {
20
+ upstreamStatus: GitUpstreamFreshness;
21
+ upstreamFetchedAt?: number;
22
+ upstreamFetchError?: string;
10
23
  }
11
24
 
12
25
  export async function getGitRepoStatus(
@@ -18,8 +31,16 @@ export async function getGitRepoStatus(
18
31
 
19
32
  try {
20
33
  const repo = await resolveGitRepository(workspace, options);
21
- const statusOutput = await runGit(repo, ['status', '--porcelain=v2', '--branch'], options);
22
- const parsed = parsePorcelainV2Status(statusOutput.stdout);
34
+ let parsed = await readPorcelainStatus(repo, options);
35
+ let upstreamProbe: GitUpstreamProbe = getInitialUpstreamProbe(parsed);
36
+
37
+ if (options.refreshUpstream) {
38
+ upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
39
+ if (upstreamProbe.upstreamStatus === 'fresh') {
40
+ parsed = await readPorcelainStatus(repo, options);
41
+ }
42
+ }
43
+
23
44
  const head = await readHead(repo, options);
24
45
  const stashCount = await readStashCount(repo, options);
25
46
 
@@ -27,6 +48,11 @@ export async function getGitRepoStatus(
27
48
  if (includeSubmodules) {
28
49
  submodules = await getSubmoduleStatuses(repo, options);
29
50
  }
51
+ const submoduleDirty = (submodules || []).some(submodule => submodule.dirty || submodule.outOfSync || !!submodule.error);
52
+ const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0
53
+ || parsed.conflictFiles.length > 0
54
+ || stashCount > 0
55
+ || submoduleDirty;
30
56
 
31
57
  return {
32
58
  workspace: repo.workspace,
@@ -36,6 +62,9 @@ export async function getGitRepoStatus(
36
62
  headCommit: head.commit,
37
63
  headMessage: head.message,
38
64
  upstream: parsed.upstream,
65
+ upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : 'no_upstream',
66
+ upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
67
+ upstreamFetchError: upstreamProbe.upstreamFetchError,
39
68
  ahead: parsed.ahead,
40
69
  behind: parsed.behind,
41
70
  staged: parsed.staged,
@@ -43,6 +72,7 @@ export async function getGitRepoStatus(
43
72
  untracked: parsed.untracked,
44
73
  deleted: parsed.deleted,
45
74
  renamed: parsed.renamed,
75
+ dirty,
46
76
  hasConflicts: parsed.conflictFiles.length > 0,
47
77
  conflictFiles: parsed.conflictFiles,
48
78
  stashCount,
@@ -74,6 +104,72 @@ interface ParsedPorcelainStatus {
74
104
  conflictFiles: string[];
75
105
  }
76
106
 
107
+ async function readPorcelainStatus(repo: ResolvedGitRepo, options: GitStatusOptions): Promise<ParsedPorcelainStatus> {
108
+ const statusOutput = await runGit(repo, ['status', '--porcelain=v2', '--branch'], options);
109
+ return parsePorcelainV2Status(statusOutput.stdout);
110
+ }
111
+
112
+ function getInitialUpstreamProbe(parsed: ParsedPorcelainStatus): GitUpstreamProbe {
113
+ return {
114
+ upstreamStatus: parsed.upstream ? 'unchecked' : 'no_upstream',
115
+ };
116
+ }
117
+
118
+ async function refreshTrackedUpstream(
119
+ repo: ResolvedGitRepo,
120
+ parsed: ParsedPorcelainStatus,
121
+ options: GitStatusOptions,
122
+ ): Promise<GitUpstreamProbe> {
123
+ if (!parsed.upstream || !parsed.branch) {
124
+ return { upstreamStatus: 'no_upstream' };
125
+ }
126
+
127
+ const remoteName = (await readBranchRemote(repo, parsed.branch, options)) ?? inferRemoteName(parsed.upstream);
128
+ if (!remoteName) {
129
+ return {
130
+ upstreamStatus: 'stale',
131
+ upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`,
132
+ };
133
+ }
134
+
135
+ try {
136
+ await runGit(repo, ['fetch', '--quiet', '--prune', '--no-tags', remoteName], options);
137
+ return {
138
+ upstreamStatus: 'fresh',
139
+ upstreamFetchedAt: Date.now(),
140
+ };
141
+ } catch (error) {
142
+ return {
143
+ upstreamStatus: 'stale',
144
+ upstreamFetchError: formatGitError(error),
145
+ };
146
+ }
147
+ }
148
+
149
+ async function readBranchRemote(repo: ResolvedGitRepo, branch: string, options: GitStatusOptions): Promise<string | null> {
150
+ try {
151
+ const result = await runGit(repo, ['config', '--get', `branch.${branch}.remote`], options);
152
+ return result.stdout.trim() || null;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ function inferRemoteName(upstream: string): string | null {
159
+ const [remoteName] = upstream.split('/');
160
+ return remoteName?.trim() || null;
161
+ }
162
+
163
+ function formatGitError(error: unknown): string {
164
+ if (error instanceof GitCommandError) {
165
+ return error.stderr || error.message;
166
+ }
167
+ if (error instanceof Error) {
168
+ return error.message;
169
+ }
170
+ return String(error);
171
+ }
172
+
77
173
  export function parsePorcelainV2Status(output: string): ParsedPorcelainStatus {
78
174
  const parsed: ParsedPorcelainStatus = {
79
175
  branch: null,
@@ -145,7 +241,7 @@ export function parsePorcelainV2Status(output: string): ParsedPorcelainStatus {
145
241
  }
146
242
 
147
243
  async function readHead(
148
- repo: { workspace: string; repoRoot: string | null; isGitRepo: boolean },
244
+ repo: ResolvedGitRepo,
149
245
  options: GitStatusOptions,
150
246
  ): Promise<{ commit: string | null; message: string | null }> {
151
247
  try {
@@ -163,7 +259,7 @@ async function readHead(
163
259
  }
164
260
 
165
261
  async function readStashCount(
166
- repo: { workspace: string; repoRoot: string | null; isGitRepo: boolean },
262
+ repo: ResolvedGitRepo,
167
263
  options: GitStatusOptions,
168
264
  ): Promise<number> {
169
265
  try {
@@ -187,6 +283,7 @@ function emptyStatus(workspace: string, lastCheckedAt: number, error: GitCommand
187
283
  headCommit: null,
188
284
  headMessage: null,
189
285
  upstream: null,
286
+ upstreamStatus: 'unavailable',
190
287
  ahead: 0,
191
288
  behind: 0,
192
289
  staged: 0,
@@ -194,6 +291,7 @@ function emptyStatus(workspace: string, lastCheckedAt: number, error: GitCommand
194
291
  untracked: 0,
195
292
  deleted: 0,
196
293
  renamed: 0,
294
+ dirty: false,
197
295
  hasConflicts: false,
198
296
  conflictFiles: [],
199
297
  stashCount: 0,
@@ -206,19 +304,41 @@ function emptyStatus(workspace: string, lastCheckedAt: number, error: GitCommand
206
304
  // ─── Submodule Status ───────────────────────────
207
305
 
208
306
  async function getSubmoduleStatuses(
209
- repo: { workspace: string; repoRoot: string | null; isGitRepo: boolean },
307
+ repo: ResolvedGitRepo,
210
308
  options: GitStatusOptions,
211
309
  ): Promise<GitSubmoduleStatus[]> {
212
310
  if (!repo.repoRoot) return [];
213
311
 
214
312
  try {
215
313
  const result = await runGit(repo, ['submodule', 'status', '--recursive'], options);
216
- return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
314
+ const submodules = parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
315
+ await Promise.all(submodules.map(submodule => enrichSubmoduleWorktreeStatus(repo, submodule, options)));
316
+ return submodules;
217
317
  } catch {
218
318
  return [];
219
319
  }
220
320
  }
221
321
 
322
+ async function enrichSubmoduleWorktreeStatus(
323
+ repo: ResolvedGitRepo,
324
+ submodule: GitSubmoduleStatus,
325
+ options: GitStatusOptions,
326
+ ): Promise<void> {
327
+ try {
328
+ const result = await runGit(repo, ['status', '--porcelain=v2', '--branch'], {
329
+ ...options,
330
+ cwd: submodule.repoPath,
331
+ });
332
+ const parsed = parsePorcelainV2Status(result.stdout);
333
+ const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0
334
+ || parsed.conflictFiles.length > 0;
335
+ submodule.dirty = submodule.dirty || dirty;
336
+ } catch (error) {
337
+ submodule.dirty = true;
338
+ submodule.error = formatGitError(error);
339
+ }
340
+ }
341
+
222
342
  function parseSubmoduleStatusOutput(
223
343
  output: string,
224
344
  repoRoot: string,
@@ -230,9 +350,9 @@ function parseSubmoduleStatusOutput(
230
350
  for (const line of output.split('\n')) {
231
351
  if (!line.trim()) continue;
232
352
 
233
- // Format: [+- ]<commit> <path> (<branch>)
234
- // - = out of sync, + = dirty, ' ' = clean
235
- const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
353
+ // Format: [+-U ]<commit> <path> (<branch>)
354
+ // - = not initialized, + = gitlink out of sync, U = conflict, ' ' = aligned.
355
+ const match = line.match(/^([\-+U\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
236
356
  if (!match) continue;
237
357
 
238
358
  const prefix = match[1];
@@ -245,8 +365,8 @@ function parseSubmoduleStatusOutput(
245
365
  path,
246
366
  commit,
247
367
  repoPath: repoRoot + '/' + path,
248
- dirty: prefix === '+',
249
- outOfSync: prefix === '-',
368
+ dirty: prefix === 'U',
369
+ outOfSync: prefix === '-' || prefix === '+',
250
370
  lastCheckedAt: Date.now(),
251
371
  });
252
372
  }
@@ -22,6 +22,9 @@ export function createGitCompactSummary(status: GitRepoStatus, diffSummary?: Git
22
22
  isGitRepo: status.isGitRepo,
23
23
  repoRoot: status.repoRoot,
24
24
  branch: status.branch,
25
+ upstreamStatus: status.upstreamStatus,
26
+ upstreamFetchedAt: status.upstreamFetchedAt,
27
+ upstreamFetchError: status.upstreamFetchError,
25
28
  dirty:
26
29
  status.staged > 0 ||
27
30
  status.modified > 0 ||
@@ -40,11 +40,19 @@ export interface GitSubmoduleStatus {
40
40
  error?: string;
41
41
  }
42
42
 
43
+ export type GitUpstreamFreshness = 'fresh' | 'unchecked' | 'stale' | 'no_upstream' | 'unavailable';
44
+
43
45
  export interface GitRepoStatus extends GitRepoIdentity {
44
46
  branch: string | null;
45
47
  headCommit: string | null;
46
48
  headMessage: string | null;
47
49
  upstream: string | null;
50
+ /** Whether ahead/behind was verified against a freshly fetched upstream ref. */
51
+ upstreamStatus: GitUpstreamFreshness;
52
+ /** Timestamp for the fetch that refreshed upstream refs when upstreamStatus === 'fresh'. */
53
+ upstreamFetchedAt?: number;
54
+ /** Error from the last refresh attempt when upstreamStatus === 'stale'. */
55
+ upstreamFetchError?: string;
48
56
  ahead: number;
49
57
  behind: number;
50
58
  staged: number;
@@ -52,6 +60,8 @@ export interface GitRepoStatus extends GitRepoIdentity {
52
60
  untracked: number;
53
61
  deleted: number;
54
62
  renamed: number;
63
+ /** Aggregate dirty flag including root worktree changes, conflicts, stash, and submodule drift. */
64
+ dirty: boolean;
55
65
  hasConflicts: boolean;
56
66
  conflictFiles: string[];
57
67
  stashCount: number;
@@ -134,6 +144,9 @@ export interface GitCompactSummary {
134
144
  isGitRepo: boolean;
135
145
  repoRoot: string | null;
136
146
  branch: string | null;
147
+ upstreamStatus: GitUpstreamFreshness;
148
+ upstreamFetchedAt?: number;
149
+ upstreamFetchError?: string;
137
150
  dirty: boolean;
138
151
  changedFiles: number;
139
152
  ahead: number;
@@ -116,8 +116,11 @@ export async function createWorktree(opts: WorktreeCreateOptions): Promise<Workt
116
116
  });
117
117
  } catch (error: any) {
118
118
  const stderr = typeof error.stderr === 'string' ? error.stderr : '';
119
- // Clean error messages for common failures
120
119
  if (/already exists/i.test(stderr)) {
120
+ // Distinguish directory-collision (TOCTOU race) from branch-already-exists
121
+ if (existsSync(targetDir)) {
122
+ throw new Error(`Worktree target directory was created concurrently: ${targetDir}`);
123
+ }
121
124
  throw new Error(`Branch '${branch}' already exists or is checked out in another worktree`);
122
125
  }
123
126
  throw new Error(`git worktree add failed: ${stderr.trim() || error.message}`);
@@ -170,6 +173,10 @@ export async function removeWorktree(repoRoot: string, worktreePath: string, opt
170
173
  const stdout = typeof error.stdout === 'string' ? error.stdout : '';
171
174
  const detail = `${stderr}\n${stdout}\n${error.message || ''}`;
172
175
  if (opts.allowSubmoduleForceFallback && SUBMODULE_WORKTREE_REMOVE_RE.test(detail)) {
176
+ process.stderr.write(
177
+ `[adhdev-mesh] WARNING: git worktree remove --force fallback for submodule worktree '${worktreePath}'. `
178
+ + `Any uncommitted changes inside submodules will be lost.\n`,
179
+ );
173
180
  try {
174
181
  await execFileAsync('git', ['worktree', 'remove', '--force', worktreePath], {
175
182
  cwd: repoRoot,