@adhdev/daemon-core 0.9.80 → 0.9.81

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.
@@ -87,6 +87,21 @@ export interface RepoMeshNodePolicy {
87
87
  * sibling paths so freshness checks stay fail-closed and non-surprising.
88
88
  */
89
89
  relatedRepos?: RepoMeshRelatedRepo[];
90
+ /**
91
+ * When true (default), mesh_git_status automatically discovers git submodules
92
+ * and includes their status. Set to false to disable auto-discovery.
93
+ */
94
+ autoDiscoverSubmodules?: boolean;
95
+ /**
96
+ * Submodule paths to ignore when autoDiscoverSubmodules is true.
97
+ * Useful for vendored dependencies that change frequently but are not deploy-critical.
98
+ */
99
+ submoduleIgnorePaths?: string[];
100
+ /**
101
+ * When true (default), mesh_clone_node runs `git submodule update --init --recursive`
102
+ * after creating a worktree. Set to false to skip submodule initialization.
103
+ */
104
+ initSubmodulesOnClone?: boolean;
90
105
  }
91
106
  export declare const DEFAULT_MESH_POLICY: RepoMeshPolicy;
92
107
  export interface RepoMeshNodeCapabilities {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.80",
3
+ "version": "0.9.81",
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",
@@ -2303,13 +2303,29 @@ export class DaemonCommandRouter {
2303
2303
  if (!node) return { success: false, error: 'Failed to register worktree node' };
2304
2304
  }
2305
2305
 
2306
+ // Initialize submodules if policy allows (default: true)
2307
+ const initSubmodules = (sourceNode.policy as any)?.initSubmodulesOnClone !== false;
2308
+ if (initSubmodules) {
2309
+ try {
2310
+ const { runGit } = await import('../git/git-executor.js');
2311
+ await runGit(
2312
+ { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true },
2313
+ ['submodule', 'update', '--init', '--recursive'],
2314
+ { timeoutMs: 120000 },
2315
+ );
2316
+ } catch (subErr: any) {
2317
+ // Submodule init is best-effort; don't fail the clone
2318
+ console.warn('[mesh] Submodule init failed for worktree:', subErr.message);
2319
+ }
2320
+ }
2321
+
2306
2322
  // Record in task ledger
2307
2323
  try {
2308
2324
  const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2309
2325
  appendLedgerEntry(meshId, {
2310
2326
  kind: 'node_cloned',
2311
2327
  nodeId: node.id,
2312
- payload: { sourceNodeId, branch: result.branch, worktreePath: result.worktreePath },
2328
+ payload: { sourceNodeId, branch: result.branch, worktreePath: result.worktreePath, submodulesInitialized: initSubmodules },
2313
2329
  });
2314
2330
  } catch { /* ledger append is best-effort */ }
2315
2331
 
@@ -1,8 +1,12 @@
1
- import type { GitRepoStatus } from './git-types.js';
1
+ import type { GitRepoStatus, GitSubmoduleStatus } from './git-types.js';
2
2
  import { GitCommandError, resolveGitRepository, runGit } from './git-executor.js';
3
3
 
4
4
  export interface GitStatusOptions {
5
5
  timeoutMs?: number;
6
+ /** When true, include submodule status in the result. Defaults to true. */
7
+ includeSubmodules?: boolean;
8
+ /** Optional filter to exclude specific submodule paths from status */
9
+ submoduleIgnorePaths?: string[];
6
10
  }
7
11
 
8
12
  export async function getGitRepoStatus(
@@ -10,6 +14,7 @@ export async function getGitRepoStatus(
10
14
  options: GitStatusOptions = {},
11
15
  ): Promise<GitRepoStatus> {
12
16
  const lastCheckedAt = Date.now();
17
+ const includeSubmodules = options.includeSubmodules !== false;
13
18
 
14
19
  try {
15
20
  const repo = await resolveGitRepository(workspace, options);
@@ -18,6 +23,11 @@ export async function getGitRepoStatus(
18
23
  const head = await readHead(repo, options);
19
24
  const stashCount = await readStashCount(repo, options);
20
25
 
26
+ let submodules: GitSubmoduleStatus[] | undefined;
27
+ if (includeSubmodules) {
28
+ submodules = await getSubmoduleStatuses(repo, options);
29
+ }
30
+
21
31
  return {
22
32
  workspace: repo.workspace,
23
33
  repoRoot: repo.repoRoot,
@@ -37,6 +47,7 @@ export async function getGitRepoStatus(
37
47
  conflictFiles: parsed.conflictFiles,
38
48
  stashCount,
39
49
  lastCheckedAt,
50
+ submodules,
40
51
  };
41
52
  } catch (error) {
42
53
  if (error instanceof GitCommandError) {
@@ -191,3 +202,54 @@ function emptyStatus(workspace: string, lastCheckedAt: number, error: GitCommand
191
202
  reason: error.reason,
192
203
  };
193
204
  }
205
+
206
+ // ─── Submodule Status ───────────────────────────
207
+
208
+ async function getSubmoduleStatuses(
209
+ repo: { workspace: string; repoRoot: string | null; isGitRepo: boolean },
210
+ options: GitStatusOptions,
211
+ ): Promise<GitSubmoduleStatus[]> {
212
+ if (!repo.repoRoot) return [];
213
+
214
+ try {
215
+ const result = await runGit(repo, ['submodule', 'status', '--recursive'], options);
216
+ return parseSubmoduleStatusOutput(result.stdout, repo.repoRoot, options.submoduleIgnorePaths);
217
+ } catch {
218
+ return [];
219
+ }
220
+ }
221
+
222
+ function parseSubmoduleStatusOutput(
223
+ output: string,
224
+ repoRoot: string,
225
+ ignorePaths?: string[],
226
+ ): GitSubmoduleStatus[] {
227
+ const submodules: GitSubmoduleStatus[] = [];
228
+ const ignoreSet = new Set(ignorePaths || []);
229
+
230
+ for (const line of output.split('\n')) {
231
+ if (!line.trim()) continue;
232
+
233
+ // Format: [+- ]<commit> <path> (<branch>)
234
+ // - = out of sync, + = dirty, ' ' = clean
235
+ const match = line.match(/^([\-+\s])([0-9a-f]{40})\s+(\S+)(?:\s+\(([^)]+)\))?/);
236
+ if (!match) continue;
237
+
238
+ const prefix = match[1];
239
+ const commit = match[2];
240
+ const path = match[3];
241
+
242
+ if (ignoreSet.has(path)) continue;
243
+
244
+ submodules.push({
245
+ path,
246
+ commit,
247
+ repoPath: repoRoot + '/' + path,
248
+ dirty: prefix === '+',
249
+ outOfSync: prefix === '-',
250
+ lastCheckedAt: Date.now(),
251
+ });
252
+ }
253
+
254
+ return submodules;
255
+ }
@@ -23,6 +23,23 @@ export interface GitRepoIdentity {
23
23
  isGitRepo: boolean;
24
24
  }
25
25
 
26
+ export interface GitSubmoduleStatus {
27
+ /** Submodule path relative to repo root */
28
+ path: string;
29
+ /** Current commit SHA the submodule is at */
30
+ commit: string;
31
+ /** Path to the submodule repo (absolute) */
32
+ repoPath: string;
33
+ /** Whether the submodule has uncommitted changes */
34
+ dirty: boolean;
35
+ /** Whether the submodule commit differs from what the parent repo expects */
36
+ outOfSync: boolean;
37
+ /** Last checked timestamp */
38
+ lastCheckedAt: number;
39
+ /** Error message if submodule status could not be read */
40
+ error?: string;
41
+ }
42
+
26
43
  export interface GitRepoStatus extends GitRepoIdentity {
27
44
  branch: string | null;
28
45
  headCommit: string | null;
@@ -39,6 +56,8 @@ export interface GitRepoStatus extends GitRepoIdentity {
39
56
  conflictFiles: string[];
40
57
  stashCount: number;
41
58
  lastCheckedAt: number;
59
+ /** Submodule statuses when auto-discover is enabled */
60
+ submodules?: GitSubmoduleStatus[];
42
61
  error?: string;
43
62
  reason?: GitFailureReason;
44
63
  }
@@ -105,6 +105,21 @@ export interface RepoMeshNodePolicy {
105
105
  * sibling paths so freshness checks stay fail-closed and non-surprising.
106
106
  */
107
107
  relatedRepos?: RepoMeshRelatedRepo[];
108
+ /**
109
+ * When true (default), mesh_git_status automatically discovers git submodules
110
+ * and includes their status. Set to false to disable auto-discovery.
111
+ */
112
+ autoDiscoverSubmodules?: boolean;
113
+ /**
114
+ * Submodule paths to ignore when autoDiscoverSubmodules is true.
115
+ * Useful for vendored dependencies that change frequently but are not deploy-critical.
116
+ */
117
+ submoduleIgnorePaths?: string[];
118
+ /**
119
+ * When true (default), mesh_clone_node runs `git submodule update --init --recursive`
120
+ * after creating a worktree. Set to false to skip submodule initialization.
121
+ */
122
+ initSubmodulesOnClone?: boolean;
108
123
  }
109
124
 
110
125
  export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {