@adhdev/daemon-core 0.9.82-rc.36 → 0.9.82-rc.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adhdev/daemon-core",
3
- "version": "0.9.82-rc.36",
3
+ "version": "0.9.82-rc.37",
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",
@@ -769,11 +769,29 @@ type MeshRefineValidationSummary = {
769
769
  outputLimitBytes: number;
770
770
  };
771
771
 
772
+ type MeshRefineStageStatus = 'passed' | 'failed' | 'skipped';
773
+
774
+ type MeshRefinePatchEquivalenceSummary = {
775
+ status: MeshRefineStageStatus;
776
+ equivalent: boolean;
777
+ baseHead: string;
778
+ branchHead: string;
779
+ mergeBase?: string;
780
+ mergedTree?: string;
781
+ expectedPatchId?: string;
782
+ actualPatchId?: string;
783
+ durationMs: number;
784
+ error?: string;
785
+ stdout?: string;
786
+ stderr?: string;
787
+ };
788
+
772
789
  const REFINE_VALIDATION_CATEGORIES = ['typecheck', 'test', 'lint', 'build'] as const;
773
790
  const REFINE_VALIDATION_TIMEOUT_MS = 120_000;
774
791
  const REFINE_VALIDATION_OUTPUT_LIMIT_BYTES = 128 * 1024;
775
792
  const REFINE_VALIDATION_SUMMARY_CHARS = 2_000;
776
793
  const REFINE_VALIDATION_MAX_COMMANDS = 4;
794
+ const REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES = 4 * 1024 * 1024;
777
795
 
778
796
  function truncateValidationOutput(value: unknown): string {
779
797
  const text = typeof value === 'string' ? value : value == null ? '' : String(value);
@@ -781,6 +799,95 @@ function truncateValidationOutput(value: unknown): string {
781
799
  return `${text.slice(0, REFINE_VALIDATION_SUMMARY_CHARS)}\n[truncated ${text.length - REFINE_VALIDATION_SUMMARY_CHARS} chars]`;
782
800
  }
783
801
 
802
+ function recordMeshRefineStage(
803
+ stages: Array<Record<string, unknown>>,
804
+ stage: string,
805
+ status: MeshRefineStageStatus,
806
+ startedAt: number,
807
+ details?: Record<string, unknown>,
808
+ ): void {
809
+ stages.push({
810
+ stage,
811
+ status,
812
+ durationMs: Date.now() - startedAt,
813
+ ...(details || {}),
814
+ });
815
+ }
816
+
817
+ async function computeGitPatchId(cwd: string, fromRef: string, toRef: string): Promise<string> {
818
+ const { execFileSync } = await import('node:child_process');
819
+ const diff = execFileSync('git', ['diff', '--patch', '--full-index', fromRef, toRef], {
820
+ cwd,
821
+ encoding: 'utf8',
822
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
823
+ });
824
+ if (!diff.trim()) return '';
825
+ const patchId = execFileSync('git', ['patch-id', '--stable'], {
826
+ cwd,
827
+ input: diff,
828
+ encoding: 'utf8',
829
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
830
+ }).trim();
831
+ return patchId.split(/\s+/)[0] || '';
832
+ }
833
+
834
+ async function runMeshRefinePatchEquivalenceGate(
835
+ repoRoot: string,
836
+ baseHead: string,
837
+ branchHead: string,
838
+ ): Promise<MeshRefinePatchEquivalenceSummary> {
839
+ const startedAt = Date.now();
840
+ try {
841
+ const { execFileSync } = await import('node:child_process');
842
+ const git = (args: string[]) => execFileSync('git', args, {
843
+ cwd: repoRoot,
844
+ encoding: 'utf8',
845
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
846
+ });
847
+ const mergeBase = git(['merge-base', baseHead, branchHead]).trim();
848
+ const mergeTreeStdout = git(['merge-tree', '--write-tree', baseHead, branchHead]);
849
+ const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || '';
850
+ if (!mergeBase || !mergedTree) {
851
+ return {
852
+ status: 'failed',
853
+ equivalent: false,
854
+ baseHead,
855
+ branchHead,
856
+ mergeBase: mergeBase || undefined,
857
+ mergedTree: mergedTree || undefined,
858
+ durationMs: Date.now() - startedAt,
859
+ error: 'patch equivalence preflight could not resolve merge-base or synthetic merge tree',
860
+ stdout: truncateValidationOutput(mergeTreeStdout),
861
+ };
862
+ }
863
+ const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
864
+ const actualPatchId = await computeGitPatchId(repoRoot, baseHead, mergedTree);
865
+ const equivalent = expectedPatchId === actualPatchId;
866
+ return {
867
+ status: equivalent ? 'passed' : 'failed',
868
+ equivalent,
869
+ baseHead,
870
+ branchHead,
871
+ mergeBase,
872
+ mergedTree,
873
+ expectedPatchId,
874
+ actualPatchId,
875
+ durationMs: Date.now() - startedAt,
876
+ };
877
+ } catch (e: any) {
878
+ return {
879
+ status: 'failed',
880
+ equivalent: false,
881
+ baseHead,
882
+ branchHead,
883
+ durationMs: Date.now() - startedAt,
884
+ error: e?.message || String(e),
885
+ stdout: truncateValidationOutput(e?.stdout),
886
+ stderr: truncateValidationOutput(e?.stderr),
887
+ };
888
+ }
889
+ }
890
+
784
891
  function readPackageScripts(workspace: string): Record<string, string> {
785
892
  try {
786
893
  const packageJsonPath = pathJoin(workspace, 'package.json');
@@ -2755,34 +2862,49 @@ export class DaemonCommandRouter {
2755
2862
  const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
2756
2863
  const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
2757
2864
  if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
2865
+ const refineStages: Array<Record<string, unknown>> = [];
2758
2866
  try {
2759
2867
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh);
2760
2868
  const mesh = meshRecord?.mesh;
2761
2869
  const node = mesh?.nodes?.find((n: any) => n.id === nodeId || n.nodeId === nodeId);
2762
- if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
2870
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh`, refineStages };
2763
2871
 
2764
2872
  if (!node.isLocalWorktree || !node.workspace) {
2765
- return { success: false, error: `Refinery requires a local worktree node` };
2873
+ return { success: false, error: `Refinery requires a local worktree node`, refineStages };
2766
2874
  }
2767
2875
 
2768
2876
  const sourceNode = node.clonedFromNodeId
2769
2877
  ? mesh?.nodes.find((n: any) => n.id === node.clonedFromNodeId || n.nodeId === node.clonedFromNodeId)
2770
2878
  : mesh?.nodes.find((n: any) => !n.isLocalWorktree);
2771
2879
  const repoRoot = sourceNode?.repoRoot || sourceNode?.workspace;
2772
- if (!repoRoot) return { success: false, error: 'Source node repoRoot not found' };
2880
+ if (!repoRoot) return { success: false, error: 'Source node repoRoot not found', refineStages };
2773
2881
 
2774
2882
  const { execFile } = await import('node:child_process');
2775
2883
  const { promisify } = await import('node:util');
2776
2884
  const execFileAsync = promisify(execFile);
2777
2885
 
2886
+ const resolveStarted = Date.now();
2778
2887
  const { stdout: branchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: node.workspace, encoding: 'utf8' });
2779
2888
  const branch = branchStdout.trim();
2780
- if (!branch) return { success: false, error: 'Could not determine branch of the worktree node' };
2889
+ if (!branch) return { success: false, error: 'Could not determine branch of the worktree node', refineStages };
2781
2890
 
2782
2891
  const { stdout: baseBranchStdout } = await execFileAsync('git', ['branch', '--show-current'], { cwd: repoRoot, encoding: 'utf8' });
2783
2892
  const baseBranch = baseBranchStdout.trim();
2893
+ const { stdout: baseHeadStdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], { cwd: repoRoot, encoding: 'utf8' });
2894
+ const { stdout: branchHeadStdout } = await execFileAsync('git', ['rev-parse', branch], { cwd: node.workspace, encoding: 'utf8' });
2895
+ const baseHead = baseHeadStdout.trim();
2896
+ const branchHead = branchHeadStdout.trim();
2897
+ recordMeshRefineStage(refineStages, 'resolve_refs', 'passed', resolveStarted, { branch, baseBranch, baseHead, branchHead });
2784
2898
 
2899
+ const validationStarted = Date.now();
2785
2900
  const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace);
2901
+ recordMeshRefineStage(
2902
+ refineStages,
2903
+ 'validation',
2904
+ validationSummary.status === 'passed' ? 'passed' : validationSummary.status === 'failed' ? 'failed' : 'skipped',
2905
+ validationStarted,
2906
+ { validationStatus: validationSummary.status, commandsRun: validationSummary.commandsRun.length },
2907
+ );
2786
2908
  if (validationSummary.status === 'failed') {
2787
2909
  return {
2788
2910
  success: false,
@@ -2792,6 +2914,7 @@ export class DaemonCommandRouter {
2792
2914
  branch,
2793
2915
  into: baseBranch,
2794
2916
  validationSummary,
2917
+ refineStages,
2795
2918
  finalBranchConvergenceState: {
2796
2919
  branch,
2797
2920
  baseBranch,
@@ -2811,6 +2934,7 @@ export class DaemonCommandRouter {
2811
2934
  branch,
2812
2935
  into: baseBranch,
2813
2936
  validationSummary,
2937
+ refineStages,
2814
2938
  finalBranchConvergenceState: {
2815
2939
  branch,
2816
2940
  baseBranch,
@@ -2822,39 +2946,127 @@ export class DaemonCommandRouter {
2822
2946
  };
2823
2947
  }
2824
2948
 
2949
+ const patchEquivalenceStarted = Date.now();
2950
+ const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
2951
+ recordMeshRefineStage(refineStages, 'patch_equivalence', patchEquivalence.status, patchEquivalenceStarted, {
2952
+ equivalent: patchEquivalence.equivalent,
2953
+ expectedPatchId: patchEquivalence.expectedPatchId,
2954
+ actualPatchId: patchEquivalence.actualPatchId,
2955
+ error: patchEquivalence.error,
2956
+ });
2957
+ if (!patchEquivalence.equivalent) {
2958
+ return {
2959
+ success: false,
2960
+ code: 'patch_equivalence_failed',
2961
+ convergenceStatus: 'blocked_review',
2962
+ error: 'Refinery patch-equivalence preflight failed; merge/refine was not attempted.',
2963
+ branch,
2964
+ into: baseBranch,
2965
+ validationSummary,
2966
+ patchEquivalence,
2967
+ refineStages,
2968
+ finalBranchConvergenceState: {
2969
+ branch,
2970
+ baseBranch,
2971
+ merged: false,
2972
+ removed: false,
2973
+ validation: 'passed',
2974
+ patchEquivalence: 'failed',
2975
+ status: 'blocked_review',
2976
+ },
2977
+ };
2978
+ }
2979
+
2980
+ let mergeResult: Record<string, unknown> | undefined;
2981
+ const mergeStarted = Date.now();
2825
2982
  try {
2826
- await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2983
+ const result = await execFileAsync('git', ['merge', '--no-ff', branch, '-m', `Auto-merge branch '${branch}' via Refinery`], { cwd: repoRoot, encoding: 'utf8' });
2984
+ mergeResult = {
2985
+ stdout: truncateValidationOutput(result.stdout),
2986
+ stderr: truncateValidationOutput(result.stderr),
2987
+ durationMs: Date.now() - mergeStarted,
2988
+ };
2989
+ recordMeshRefineStage(refineStages, 'merge', 'passed', mergeStarted, mergeResult);
2827
2990
  } catch (e: any) {
2991
+ recordMeshRefineStage(refineStages, 'merge', 'failed', mergeStarted, {
2992
+ error: e?.message || String(e),
2993
+ stdout: truncateValidationOutput(e?.stdout),
2994
+ stderr: truncateValidationOutput(e?.stderr),
2995
+ });
2828
2996
  return {
2829
2997
  success: false,
2830
2998
  error: `Merge failed (conflicts?): ${e.message}`,
2831
2999
  validationSummary,
3000
+ patchEquivalence,
3001
+ refineStages,
2832
3002
  finalBranchConvergenceState: {
2833
3003
  branch,
2834
3004
  baseBranch,
2835
3005
  merged: false,
2836
3006
  removed: false,
2837
3007
  validation: 'passed',
3008
+ patchEquivalence: 'passed',
2838
3009
  status: 'not_mergeable',
2839
3010
  },
2840
3011
  };
2841
3012
  }
2842
3013
 
3014
+ const cleanupStarted = Date.now();
2843
3015
  const removeResult = await this.execute('remove_mesh_node', {
2844
3016
  meshId,
2845
3017
  nodeId,
2846
- sessionCleanupMode: 'kill',
3018
+ sessionCleanupMode: 'preserve',
2847
3019
  inlineMesh: args?.inlineMesh,
2848
3020
  });
3021
+ recordMeshRefineStage(refineStages, 'cleanup', removeResult?.success === false ? 'failed' : 'passed', cleanupStarted, {
3022
+ removed: removeResult?.removed,
3023
+ code: removeResult?.code,
3024
+ error: removeResult?.error,
3025
+ });
2849
3026
 
3027
+ let ledgerError: string | undefined;
3028
+ const ledgerStarted = Date.now();
2850
3029
  try {
2851
3030
  const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
2852
3031
  appendLedgerEntry(meshId, {
2853
3032
  kind: 'node_removed',
2854
3033
  nodeId,
2855
- payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary },
3034
+ payload: { refined: true, mergedBranch: branch, into: baseBranch, validationSummary, patchEquivalence },
2856
3035
  });
2857
- } catch {}
3036
+ recordMeshRefineStage(refineStages, 'ledger', 'passed', ledgerStarted);
3037
+ } catch (e: any) {
3038
+ ledgerError = e?.message || String(e);
3039
+ recordMeshRefineStage(refineStages, 'ledger', 'failed', ledgerStarted, { error: ledgerError });
3040
+ }
3041
+
3042
+ const finalBranchConvergenceState = {
3043
+ branch: baseBranch,
3044
+ mergedBranch: branch,
3045
+ baseBranch,
3046
+ merged: true,
3047
+ removed: removeResult?.success !== false,
3048
+ validation: 'passed',
3049
+ patchEquivalence: 'passed',
3050
+ status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
3051
+ };
3052
+
3053
+ if (removeResult?.success === false) {
3054
+ return {
3055
+ success: false,
3056
+ code: 'cleanup_failed',
3057
+ error: 'Refinery merge completed but worktree cleanup failed; manual cleanup/retry is required.',
3058
+ merged: true,
3059
+ branch,
3060
+ into: baseBranch,
3061
+ removeResult,
3062
+ validationSummary,
3063
+ patchEquivalence,
3064
+ mergeResult,
3065
+ refineStages,
3066
+ ...(ledgerError ? { ledgerError } : {}),
3067
+ finalBranchConvergenceState,
3068
+ };
3069
+ }
2858
3070
 
2859
3071
  return {
2860
3072
  success: true,
@@ -2863,18 +3075,14 @@ export class DaemonCommandRouter {
2863
3075
  into: baseBranch,
2864
3076
  removeResult,
2865
3077
  validationSummary,
2866
- finalBranchConvergenceState: {
2867
- branch: baseBranch,
2868
- mergedBranch: branch,
2869
- baseBranch,
2870
- merged: true,
2871
- removed: removeResult?.success !== false,
2872
- validation: 'passed',
2873
- status: removeResult?.success === false ? 'merged_cleanup_failed' : 'merged',
2874
- },
3078
+ patchEquivalence,
3079
+ mergeResult,
3080
+ refineStages,
3081
+ ...(ledgerError ? { ledgerError } : {}),
3082
+ finalBranchConvergenceState,
2875
3083
  };
2876
3084
  } catch (e: any) {
2877
- return { success: false, error: e.message };
3085
+ return { success: false, error: e.message, refineStages };
2878
3086
  }
2879
3087
  }
2880
3088