@adhdev/daemon-core 0.9.82-rc.81 → 0.9.82-rc.83

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.
@@ -12,6 +12,13 @@ export interface RepoMeshRefineValidationCommandConfig {
12
12
  }
13
13
  export interface RepoMeshRefineConfig {
14
14
  version: 1;
15
+ /**
16
+ * Narrow Refinery opt-in for monorepos with submodule gitlinks.
17
+ * When true, Refinery may non-force publish unreachable submodule gitlink
18
+ * commits to the submodule remote main branch after validation and
19
+ * patch-equivalence pass, then verify remote-main reachability.
20
+ */
21
+ allowAutoPublishSubmoduleMainCommits?: boolean;
15
22
  validation?: {
16
23
  required?: boolean;
17
24
  commands?: RepoMeshRefineValidationCommandConfig[];
@@ -54,6 +61,11 @@ export declare const MESH_REFINE_CONFIG_SCHEMA: {
54
61
  readonly version: {
55
62
  readonly const: 1;
56
63
  };
64
+ readonly allowAutoPublishSubmoduleMainCommits: {
65
+ readonly type: "boolean";
66
+ readonly default: false;
67
+ readonly description: "When true, Refinery may non-force publish submodule gitlink commits referenced by the refined root tree to each submodule origin/main after validation and patch-equivalence pass, then verify reachability.";
68
+ };
57
69
  readonly validation: {
58
70
  readonly type: "object";
59
71
  readonly additionalProperties: false;
@@ -74,6 +74,13 @@ export interface RepoMeshPolicy {
74
74
  requirePreTaskCheckpoint: boolean;
75
75
  requirePostTaskCheckpoint: boolean;
76
76
  requireApprovalForPush: boolean;
77
+ /**
78
+ * Narrow Refinery opt-in: when validation and patch-equivalence have passed,
79
+ * allow Refinery to publish submodule gitlink commits to each submodule's
80
+ * configured remote main branch with a non-force push, then verify reachability.
81
+ * Defaults to false; root branch pushes/merges are not affected.
82
+ */
83
+ allowAutoPublishSubmoduleMainCommits?: boolean;
77
84
  requireApprovalForDestructiveGit: boolean;
78
85
  dirtyWorkspaceBehavior: 'block' | 'warn' | 'checkpoint_then_continue';
79
86
  maxParallelTasks: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.81",
3
+ "version": "0.9.82-rc.83",
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",
@@ -1035,7 +1035,7 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1035
1035
  ptyStatusApprovalOnly: false,
1036
1036
  });
1037
1037
 
1038
- if (supportsCliNativeTranscript(providerType)) {
1038
+ if (supportsCliNativeTranscript(providerType) && (provider?.canonicalHistory as any)?.mode === 'native-source') {
1039
1039
  const agentStr = provider?.type || args?.agentType || getCurrentProviderType(h, adapter.cliType);
1040
1040
  const workspace = typeof args?.workspace === 'string'
1041
1041
  ? args.workspace
@@ -1220,6 +1220,18 @@ export async function handleReadChat(h: CommandHelpers, args: any): Promise<Comm
1220
1220
  freshEnough: true,
1221
1221
  ptyStatusApprovalOnly: false,
1222
1222
  });
1223
+ const requiresNativeSource = supportsCliNativeTranscript(agentStr)
1224
+ && (provider?.canonicalHistory as any)?.mode === 'native-source';
1225
+ if (requiresNativeSource && !nativeSelected) {
1226
+ return {
1227
+ success: false,
1228
+ code: 'native_history_not_safely_available',
1229
+ error: 'Provider-native history was not safely available for the requested CLI session.',
1230
+ providerSessionId: historyProviderSessionId,
1231
+ messageSource,
1232
+ transcriptProvenance: messageSource,
1233
+ };
1234
+ }
1223
1235
  return buildReadChatCommandResult({
1224
1236
  messages: historyMessages,
1225
1237
  status: 'idle',
@@ -109,6 +109,15 @@ function countMessages(value: unknown): number {
109
109
  return Array.isArray(value) ? value.length : 0;
110
110
  }
111
111
 
112
+ function hasFinalAssistantMessage(value: unknown): boolean {
113
+ const messages = Array.isArray(value) ? value : [];
114
+ const last = messages[messages.length - 1] as any;
115
+ if (!last || last.role !== 'assistant') return false;
116
+ if (last.bubbleState === 'streaming') return false;
117
+ if (last.meta?.streaming === true) return false;
118
+ return typeof last.content === 'string' && last.content.trim().length > 0;
119
+ }
120
+
112
121
  function hasZeroMessageStartingLaunch(adapter: any): boolean {
113
122
  const adapterStatus = adapter?.getStatus?.({ allowParse: false }) ?? adapter?.getStatus?.() ?? {};
114
123
  const parsedStatus = typeof adapter?.getScriptParsedStatus === 'function'
@@ -123,6 +132,21 @@ function hasZeroMessageStartingLaunch(adapter: any): boolean {
123
132
  return !hasAdapterPendingResponse(adapter);
124
133
  }
125
134
 
135
+ function hasCompletedStartingLaunch(adapter: any): boolean {
136
+ const adapterStatus = adapter?.getStatus?.({ allowParse: false }) ?? adapter?.getStatus?.() ?? {};
137
+ const adapterRawStatus = normalizeAgentStatus(adapterStatus?.status);
138
+ if (adapterRawStatus !== 'starting') return false;
139
+ if (hasAdapterPendingResponse(adapter)) return false;
140
+
141
+ const parsedStatus = typeof adapter?.getScriptParsedStatus === 'function'
142
+ ? adapter.getScriptParsedStatus()
143
+ : {};
144
+ const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
145
+ if (parsedRawStatus !== 'idle') return false;
146
+ if (hasNonEmptyModalButtons(adapterStatus?.activeModal ?? adapterStatus?.modal ?? parsedStatus?.activeModal ?? parsedStatus?.modal)) return false;
147
+ return hasFinalAssistantMessage(parsedStatus?.messages);
148
+ }
149
+
126
150
  function shouldSuppressStaleParsedBusyStatus(adapterStatus: string, parsedStatus: any, adapter: any): boolean {
127
151
  const parsedRawStatus = normalizeAgentStatus(parsedStatus?.status);
128
152
  if (!BUSY_AGENT_STATUSES.has(parsedRawStatus)) return false;
@@ -133,6 +157,7 @@ function shouldSuppressStaleParsedBusyStatus(adapterStatus: string, parsedStatus
133
157
 
134
158
  function getEffectiveAgentSendStatus(adapter: any): string {
135
159
  const adapterStatus = normalizeAgentStatus(adapter?.getStatus?.({ allowParse: false })?.status ?? adapter?.getStatus?.()?.status);
160
+ if (adapterStatus === 'starting' && hasCompletedStartingLaunch(adapter)) return 'idle';
136
161
  if (adapterStatus && adapterStatus !== 'idle') return adapterStatus;
137
162
  if (adapterStatus !== 'idle') return adapterStatus;
138
163
 
@@ -1102,6 +1102,11 @@ type MeshRefineSubmoduleReachabilityEntry = {
1102
1102
  commit: string;
1103
1103
  reachable: boolean;
1104
1104
  publishRequired?: boolean;
1105
+ autoPublishAllowed?: boolean;
1106
+ autoPublishAttempted?: boolean;
1107
+ autoPublishSucceeded?: boolean;
1108
+ autoPublishVerified?: boolean;
1109
+ autoPublishRefspec?: string;
1105
1110
  checkedLocal?: boolean;
1106
1111
  localReachable?: boolean;
1107
1112
  remote?: string;
@@ -1111,6 +1116,8 @@ type MeshRefineSubmoduleReachabilityEntry = {
1111
1116
  remoteMainReachable?: boolean;
1112
1117
  fetchedFromOrigin?: boolean;
1113
1118
  error?: string;
1119
+ publishStdout?: string;
1120
+ publishStderr?: string;
1114
1121
  };
1115
1122
 
1116
1123
  type MeshRefineSubmoduleReachabilitySummary = {
@@ -1119,6 +1126,8 @@ type MeshRefineSubmoduleReachabilitySummary = {
1119
1126
  unreachable: MeshRefineSubmoduleReachabilityEntry[];
1120
1127
  entries: MeshRefineSubmoduleReachabilityEntry[];
1121
1128
  durationMs: number;
1129
+ autoPublishAllowed?: boolean;
1130
+ autoPublishPolicySource?: string;
1122
1131
  error?: string;
1123
1132
  };
1124
1133
 
@@ -1187,6 +1196,17 @@ function buildSubmodulePublishRequiredNextStep(entries: MeshRefineSubmoduleReach
1187
1196
  return `Ask the user for explicit approval to push/publish the unreachable submodule commit(s) (${refs}) to the configured submodule remote main branch, then rerun mesh_refine_node. Do not merge the root branch until every submodule gitlink commit is reachable from submodule origin/main.`;
1188
1197
  }
1189
1198
 
1199
+ function resolveRefineryAutoPublishSubmoduleMainCommits(mesh: any, workspace: string): { enabled: boolean; source?: string } {
1200
+ if (mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true) {
1201
+ return { enabled: true, source: 'mesh.policy.allowAutoPublishSubmoduleMainCommits' };
1202
+ }
1203
+ const loaded = loadMeshRefineConfig(mesh, workspace);
1204
+ if (loaded.config?.allowAutoPublishSubmoduleMainCommits === true) {
1205
+ return { enabled: true, source: loaded.path || loaded.source };
1206
+ }
1207
+ return { enabled: false };
1208
+ }
1209
+
1190
1210
  async function computeGitPatchId(cwd: string, fromRef: string, toRef: string): Promise<string> {
1191
1211
  const { execFileSync } = await import('node:child_process');
1192
1212
  const diff = execFileSync('git', ['diff', '--patch', '--full-index', fromRef, toRef], {
@@ -1264,6 +1284,7 @@ async function runMeshRefinePatchEquivalenceGate(
1264
1284
  async function runMeshRefineSubmoduleReachabilityGate(
1265
1285
  repoRoot: string,
1266
1286
  mergedTree: string,
1287
+ options: { allowAutoPublishSubmoduleMainCommits?: boolean; autoPublishPolicySource?: string } = {},
1267
1288
  ): Promise<MeshRefineSubmoduleReachabilitySummary> {
1268
1289
  const startedAt = Date.now();
1269
1290
  const entries: MeshRefineSubmoduleReachabilityEntry[] = [];
@@ -1285,6 +1306,17 @@ async function runMeshRefineSubmoduleReachabilityGate(
1285
1306
  await runGit(submodulePath, ['-c', 'protocol.file.allow=always', 'fetch', 'origin', `refs/heads/${branch}:refs/remotes/origin/${branch}`]);
1286
1307
  await runGit(submodulePath, ['merge-base', '--is-ancestor', commit, `refs/remotes/origin/${branch}`]);
1287
1308
  };
1309
+ const publishCommitToRemoteMain = async (submodulePath: string, commit: string, branch = 'main'): Promise<{ stdout: string; stderr: string; refspec: string }> => {
1310
+ const refspec = `${commit}:refs/heads/${branch}`;
1311
+ const { stdout, stderr } = await execFileAsync('git', ['push', 'origin', refspec], {
1312
+ cwd: submodulePath,
1313
+ encoding: 'utf8',
1314
+ timeout: 30_000,
1315
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
1316
+ windowsHide: true,
1317
+ });
1318
+ return { stdout: String(stdout || ''), stderr: String(stderr || ''), refspec };
1319
+ };
1288
1320
 
1289
1321
  const treeOutput = await runGit(repoRoot, ['ls-tree', '-r', '-z', mergedTree]);
1290
1322
  const gitlinks = treeOutput
@@ -1334,11 +1366,43 @@ async function runMeshRefineSubmoduleReachabilityGate(
1334
1366
  continue;
1335
1367
  }
1336
1368
  entry.remoteMainBranch = 'main';
1337
- await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, 'main');
1338
- entry.fetchedFromOrigin = true;
1339
- entry.remoteReachable = true;
1340
- entry.remoteMainReachable = true;
1341
- entry.reachable = true;
1369
+ try {
1370
+ await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, 'main');
1371
+ entry.fetchedFromOrigin = true;
1372
+ entry.remoteReachable = true;
1373
+ entry.remoteMainReachable = true;
1374
+ entry.reachable = true;
1375
+ } catch (e: any) {
1376
+ entry.remoteReachable = false;
1377
+ entry.remoteMainReachable = false;
1378
+ entry.publishRequired = true;
1379
+ const details = truncateValidationOutput(e?.stderr || e?.message || String(e));
1380
+ entry.error = `Submodule remote main reachability check failed for origin/main: ${details}`;
1381
+ if (options.allowAutoPublishSubmoduleMainCommits === true && entry.localReachable === true) {
1382
+ entry.autoPublishAllowed = true;
1383
+ entry.autoPublishAttempted = true;
1384
+ try {
1385
+ const publish = await publishCommitToRemoteMain(submodulePath, gitlink.commit, 'main');
1386
+ entry.autoPublishRefspec = publish.refspec;
1387
+ entry.publishStdout = truncateValidationOutput(publish.stdout);
1388
+ entry.publishStderr = truncateValidationOutput(publish.stderr);
1389
+ entry.autoPublishSucceeded = true;
1390
+ await verifyRemoteMainContainsCommit(submodulePath, gitlink.commit, 'main');
1391
+ entry.fetchedFromOrigin = true;
1392
+ entry.remoteReachable = true;
1393
+ entry.remoteMainReachable = true;
1394
+ entry.autoPublishVerified = true;
1395
+ entry.publishRequired = false;
1396
+ entry.reachable = true;
1397
+ entry.error = undefined;
1398
+ } catch (publishError: any) {
1399
+ entry.autoPublishSucceeded = false;
1400
+ entry.autoPublishVerified = false;
1401
+ const publishDetails = truncateValidationOutput(publishError?.stderr || publishError?.message || String(publishError));
1402
+ entry.error = `Submodule auto-publish to origin/main failed or could not be verified: ${publishDetails}`;
1403
+ }
1404
+ }
1405
+ }
1342
1406
  } catch (e: any) {
1343
1407
  entry.remoteReachable = false;
1344
1408
  entry.remoteMainReachable = false;
@@ -1360,6 +1424,8 @@ async function runMeshRefineSubmoduleReachabilityGate(
1360
1424
  unreachable: unreachable.map(entry => ({ ...entry, publishRequired: entry.publishRequired !== false })),
1361
1425
  entries: entries.map(entry => entry.reachable ? entry : { ...entry, publishRequired: entry.publishRequired !== false }),
1362
1426
  durationMs: Date.now() - startedAt,
1427
+ autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
1428
+ autoPublishPolicySource: options.autoPublishPolicySource,
1363
1429
  };
1364
1430
  } catch (e: any) {
1365
1431
  const unreachable = entries.filter(entry => !entry.reachable).map(entry => ({ ...entry, publishRequired: true }));
@@ -1369,6 +1435,8 @@ async function runMeshRefineSubmoduleReachabilityGate(
1369
1435
  unreachable,
1370
1436
  entries: entries.map(entry => entry.reachable ? entry : { ...entry, publishRequired: true }),
1371
1437
  durationMs: Date.now() - startedAt,
1438
+ autoPublishAllowed: options.allowAutoPublishSubmoduleMainCommits === true,
1439
+ autoPublishPolicySource: options.autoPublishPolicySource,
1372
1440
  error: truncateValidationOutput(e?.message || String(e)),
1373
1441
  };
1374
1442
  }
@@ -2753,13 +2821,38 @@ export class DaemonCommandRouter {
2753
2821
  }
2754
2822
 
2755
2823
  const submoduleReachabilityStarted = Date.now();
2756
- const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead);
2824
+ const autoPublishSubmoduleMainCommits = resolveRefineryAutoPublishSubmoduleMainCommits(mesh, node.workspace);
2825
+ const submoduleReachability = await runMeshRefineSubmoduleReachabilityGate(repoRoot, patchEquivalence.mergedTree || branchHead, {
2826
+ allowAutoPublishSubmoduleMainCommits: autoPublishSubmoduleMainCommits.enabled,
2827
+ autoPublishPolicySource: autoPublishSubmoduleMainCommits.source,
2828
+ });
2757
2829
  recordMeshRefineStage(refineStages, 'submodule_reachability', submoduleReachability.status, submoduleReachabilityStarted, {
2758
2830
  checked: submoduleReachability.checked,
2831
+ autoPublishAllowed: submoduleReachability.autoPublishAllowed,
2832
+ autoPublishPolicySource: submoduleReachability.autoPublishPolicySource,
2833
+ autoPublished: submoduleReachability.entries
2834
+ .filter(entry => entry.autoPublishAttempted)
2835
+ .map(entry => ({
2836
+ path: entry.path,
2837
+ commit: entry.commit,
2838
+ remote: entry.remote,
2839
+ remoteUrl: entry.remoteUrl,
2840
+ remoteMainBranch: entry.remoteMainBranch,
2841
+ refspec: entry.autoPublishRefspec,
2842
+ succeeded: entry.autoPublishSucceeded,
2843
+ verified: entry.autoPublishVerified,
2844
+ remoteMainReachable: entry.remoteMainReachable,
2845
+ error: entry.error,
2846
+ })),
2759
2847
  unreachable: submoduleReachability.unreachable.map(entry => ({
2760
2848
  path: entry.path,
2761
2849
  commit: entry.commit,
2762
2850
  publishRequired: entry.publishRequired === true,
2851
+ autoPublishAllowed: entry.autoPublishAllowed,
2852
+ autoPublishAttempted: entry.autoPublishAttempted,
2853
+ autoPublishSucceeded: entry.autoPublishSucceeded,
2854
+ autoPublishVerified: entry.autoPublishVerified,
2855
+ autoPublishRefspec: entry.autoPublishRefspec,
2763
2856
  remote: entry.remote,
2764
2857
  remoteUrl: entry.remoteUrl,
2765
2858
  remoteReachable: entry.remoteReachable,
@@ -2793,6 +2886,11 @@ export class DaemonCommandRouter {
2793
2886
  remoteReachable: entry.remoteReachable,
2794
2887
  remoteMainBranch: entry.remoteMainBranch,
2795
2888
  remoteMainReachable: entry.remoteMainReachable,
2889
+ autoPublishAllowed: entry.autoPublishAllowed,
2890
+ autoPublishAttempted: entry.autoPublishAttempted,
2891
+ autoPublishSucceeded: entry.autoPublishSucceeded,
2892
+ autoPublishVerified: entry.autoPublishVerified,
2893
+ autoPublishRefspec: entry.autoPublishRefspec,
2796
2894
  error: entry.error,
2797
2895
  })),
2798
2896
  branch,
@@ -2870,7 +2968,7 @@ export class DaemonCommandRouter {
2870
2968
  appendLedgerEntry(meshId, {
2871
2969
  kind: 'node_removed',
2872
2970
  nodeId,
2873
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence },
2971
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence, submoduleReachability },
2874
2972
  });
2875
2973
  recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
2876
2974
  } catch (e: any) {
@@ -2900,6 +2998,7 @@ export class DaemonCommandRouter {
2900
2998
  removeResult,
2901
2999
  validationSummary,
2902
3000
  patchEquivalence,
3001
+ submoduleReachability,
2903
3002
  mergeResult,
2904
3003
  refineStages,
2905
3004
  ...(ledgerError ? { ledgerError } : {}),
@@ -2915,6 +3014,7 @@ export class DaemonCommandRouter {
2915
3014
  removeResult,
2916
3015
  validationSummary,
2917
3016
  patchEquivalence,
3017
+ submoduleReachability,
2918
3018
  mergeResult,
2919
3019
  refineStages,
2920
3020
  ...(ledgerError ? { ledgerError } : {}),
@@ -87,6 +87,7 @@ function mergeMeshPolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMe
87
87
  }
88
88
  const maxParallelTasks = Number(policy.maxParallelTasks);
89
89
  policy.maxParallelTasks = Number.isFinite(maxParallelTasks) ? Math.max(1, Math.min(8, Math.floor(maxParallelTasks))) : 2;
90
+ policy.allowAutoPublishSubmoduleMainCommits = policy.allowAutoPublishSubmoduleMainCommits === true;
90
91
  if (!SESSION_CLEANUP_MODES.has(String(policy.sessionCleanupOnNodeRemove))) {
91
92
  policy.sessionCleanupOnNodeRemove = 'preserve';
92
93
  }
@@ -127,6 +127,9 @@ function buildPolicySection(policy: RepoMeshPolicy): string {
127
127
  if (policy.requirePreTaskCheckpoint) rules.push('- Create a git checkpoint **before** starting each task');
128
128
  if (policy.requirePostTaskCheckpoint) rules.push('- Create a git checkpoint **after** each task completes');
129
129
  if (policy.requireApprovalForPush) rules.push('- **Ask for user approval** before pushing to remote');
130
+ if (policy.allowAutoPublishSubmoduleMainCommits) {
131
+ rules.push('- Refinery may auto-publish unreachable submodule gitlink commits to submodule origin/main with non-force pushes after validation and patch-equivalence pass');
132
+ }
130
133
  if (policy.requireApprovalForDestructiveGit) rules.push('- **Ask for user approval** before destructive git operations (force push, reset, etc.)');
131
134
 
132
135
  const dirtyBehavior = {
@@ -182,7 +185,7 @@ const WORKFLOW_SECTION = `## Orchestration Workflow
182
185
  4. **Monitor** — Prefer event-driven completion/status notifications. Do **not** poll \`mesh_read_chat\` repeatedly. Use \`mesh_view_queue\` to see the status of all pending, assigned, completed, and failed tasks. Do not call \`mesh_read_chat\` again within a few seconds for the same generating session. Use at most one compact \`mesh_read_chat\` check after a completion/approval signal. Handle approvals via \`mesh_approve\`.
183
186
  5. **Verify** — When a task reports completion or git work is visible, call \`mesh_git_status\` to verify changes were made.
184
187
  6. **Checkpoint** — Call \`mesh_checkpoint\` to save the work.
185
- 7. **Converge branches** — Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from the configured submodule remote main branch, not merely present on a feature ref or local checkout. If \`mesh_refine_node\` returns \`submodule_reachability_failed\` or publish-required evidence, keep the public convergence bucket as \`blocked_review\`, ask the user for explicit approval to push/publish the unreachable submodule commit(s) to submodule main, then rerun \`mesh_refine_node\`; do not merge the root branch until the submodule commit(s) are reachable from submodule origin/main. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
188
+ 7. **Converge branches** — Before marking any task complete, classify every touched node/branch into exactly one final state: \`merged_to_main\`, \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\`. Use \`mesh_status\` branchConvergenceSummary. For obvious clean branch catch-up (ahead 0, behind > 0, upstream fresh, no dirty/stash/submodule issues), use \`mesh_fast_forward_node\` dry-run first and execute only when explicitly safe/approved; this avoids consuming an agent session. Use \`mesh_refine_node\` for clean worktree branches when safe. Before/refine merging root commits that contain submodule gitlink changes, require each submodule commit to be reachable from the configured submodule remote main branch, not merely present on a feature ref or local checkout. If \`mesh_refine_node\` returns \`submodule_reachability_failed\` or publish-required evidence, keep the public convergence bucket as \`blocked_review\`; unless \`allowAutoPublishSubmoduleMainCommits\` is explicitly enabled and Refinery reports successful non-force publish plus post-publish verification, ask the user for explicit approval to push/publish the unreachable submodule commit(s) to submodule main, then rerun \`mesh_refine_node\`. Do not merge the root branch until the submodule commit(s) are reachable from submodule origin/main. A task that remains on a non-main branch is not fully complete unless the final report names the follow-up state and next step.
186
189
  8. **Clean up** — Remove worktree nodes via \`mesh_remove_node\` after their work is merged or no longer needed.
187
190
  9. **Report** — Summarize what was done, what changed, any issues, and the branch convergence state.
188
191
 
@@ -221,6 +224,6 @@ function buildRulesSection(coordinatorCliType?: string): string {
221
224
  - **Clean up worktree nodes.** After a worktree task completes and its changes are merged or checkpointed, call \`mesh_remove_node\` to free resources.
222
225
  - **Do not strand completed branches.** A checkpointed or clean feature/worktree branch is not done by itself. Merge/refine it to the mesh default branch, fast-forward obvious clean behind-only branches with \`mesh_fast_forward_node\`, or explicitly report one of \`pushed_feature_branch_needs_merge\`, \`blocked_review\`, \`cleanup_candidate\`, or \`not_mergeable\` with the next action.
223
226
  - **Keep Refinery validation project-configurable.** \`mesh_refine_node\` must execute validation from repo mesh/refine config (for example \`.adhdev/refine.{json,yaml,yml}\`, \`.adhdev/repo-mesh-refine.*\`, or \`repo-mesh.refine.*\`). Heuristics are suggestions/scaffolding only, not the execution path.
224
- - **Treat submodule main reachability as publish-needed.** A \`submodule_reachability_failed\` refine result means the root gitlink points at a submodule commit that is not reachable from the configured submodule remote main branch. Do not treat feature-branch reachability as complete, retry validation blindly, or start code review first. Classify it as \`blocked_review\`, request user approval to push/publish the submodule commit to submodule main, then rerun \`mesh_refine_node\`.
227
+ - **Treat submodule main reachability as publish-needed.** A \`submodule_reachability_failed\` refine result means the root gitlink points at a submodule commit that is not reachable from the configured submodule remote main branch. Do not treat feature-branch reachability as complete, retry validation blindly, or start code review first. Classify it as \`blocked_review\`, request user approval to push/publish the submodule commit to submodule main, then rerun \`mesh_refine_node\`, unless the mesh or repo refine config explicitly enabled \`allowAutoPublishSubmoduleMainCommits\` and Refinery reports exact path/commit/remote/branch evidence with post-publish verification.
225
228
  - **Name worktree branches meaningfully.** Use descriptive names like \`feat/auth-refactor\` or \`fix/build-123\`.${coordinatorNote}`;
226
229
  }
@@ -914,7 +914,8 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
914
914
  const providerSessionId = readNonEmptyString(args.metadataEvent.providerSessionId) || undefined;
915
915
  const finalSummary = readNonEmptyString(args.metadataEvent.finalSummary) || undefined;
916
916
  const workerResult = readWorkerResultMetadata(args.metadataEvent);
917
- const completedTask = sessionId
917
+ const hasCompletionEvidence = !!finalSummary || !!workerResult;
918
+ const completedTask = sessionId && hasCompletionEvidence
918
919
  ? updateSessionTaskStatus(args.meshId, sessionId, 'completed')
919
920
  : null;
920
921
  if (completedTask) {
@@ -18,6 +18,13 @@ export interface RepoMeshRefineValidationCommandConfig {
18
18
 
19
19
  export interface RepoMeshRefineConfig {
20
20
  version: 1;
21
+ /**
22
+ * Narrow Refinery opt-in for monorepos with submodule gitlinks.
23
+ * When true, Refinery may non-force publish unreachable submodule gitlink
24
+ * commits to the submodule remote main branch after validation and
25
+ * patch-equivalence pass, then verify remote-main reachability.
26
+ */
27
+ allowAutoPublishSubmoduleMainCommits?: boolean;
21
28
  validation?: {
22
29
  required?: boolean;
23
30
  commands?: RepoMeshRefineValidationCommandConfig[];
@@ -73,6 +80,11 @@ export const MESH_REFINE_CONFIG_SCHEMA = {
73
80
  required: ['version'],
74
81
  properties: {
75
82
  version: { const: 1 },
83
+ allowAutoPublishSubmoduleMainCommits: {
84
+ type: 'boolean',
85
+ default: false,
86
+ description: 'When true, Refinery may non-force publish submodule gitlink commits referenced by the refined root tree to each submodule origin/main after validation and patch-equivalence pass, then verify reachability.',
87
+ },
76
88
  validation: {
77
89
  type: 'object',
78
90
  additionalProperties: false,
@@ -179,6 +191,9 @@ export function validateMeshRefineConfig(config: unknown, source = 'inline'): {
179
191
 
180
192
  if (!isRecord(config)) return { valid: false, errors: ['config must be an object'], commands, rejectedCommands };
181
193
  if (config.version !== 1) errors.push('version must be 1');
194
+ if (config.allowAutoPublishSubmoduleMainCommits !== undefined && typeof config.allowAutoPublishSubmoduleMainCommits !== 'boolean') {
195
+ errors.push('allowAutoPublishSubmoduleMainCommits must be a boolean when provided');
196
+ }
182
197
  const validation = config.validation;
183
198
  if (validation !== undefined && !isRecord(validation)) errors.push('validation must be an object');
184
199
  const rawCommands = isRecord(validation) ? validation.commands : undefined;
@@ -94,6 +94,13 @@ export interface RepoMeshPolicy {
94
94
  requirePreTaskCheckpoint: boolean;
95
95
  requirePostTaskCheckpoint: boolean;
96
96
  requireApprovalForPush: boolean;
97
+ /**
98
+ * Narrow Refinery opt-in: when validation and patch-equivalence have passed,
99
+ * allow Refinery to publish submodule gitlink commits to each submodule's
100
+ * configured remote main branch with a non-force push, then verify reachability.
101
+ * Defaults to false; root branch pushes/merges are not affected.
102
+ */
103
+ allowAutoPublishSubmoduleMainCommits?: boolean;
97
104
  requireApprovalForDestructiveGit: boolean;
98
105
  dirtyWorkspaceBehavior: 'block' | 'warn' | 'checkpoint_then_continue';
99
106
  maxParallelTasks: number;
@@ -158,6 +165,7 @@ export const DEFAULT_MESH_POLICY: RepoMeshPolicy = {
158
165
  requirePreTaskCheckpoint: false,
159
166
  requirePostTaskCheckpoint: true,
160
167
  requireApprovalForPush: true,
168
+ allowAutoPublishSubmoduleMainCommits: false,
161
169
  requireApprovalForDestructiveGit: true,
162
170
  dirtyWorkspaceBehavior: 'warn',
163
171
  maxParallelTasks: 2,