@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/dist/index.js +192 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +192 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/commands/router.ts +226 -18
package/package.json
CHANGED
package/src/commands/router.ts
CHANGED
|
@@ -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: '
|
|
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
|
-
|
|
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
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
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
|
|