@adhdev/daemon-core 0.9.82-rc.553 → 0.9.82-rc.554
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/cli-adapter-types.d.ts +29 -0
- package/dist/index.js +341 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +341 -11
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-refine-gates.d.ts +86 -0
- package/dist/providers/cli-provider-instance.d.ts +16 -1
- package/dist/providers/spec/adapter.d.ts +7 -0
- package/dist/providers/spec/cli-adapter.d.ts +64 -0
- package/dist/providers/spec/fsm-driver.d.ts +11 -0
- package/package.json +3 -3
- package/src/cli-adapter-types.ts +29 -0
- package/src/commands/router-refine.ts +40 -1
- package/src/mesh/mesh-refine-gates.ts +256 -0
- package/src/providers/cli-provider-instance.ts +83 -8
- package/src/providers/spec/adapter.ts +7 -0
- package/src/providers/spec/cli-adapter.ts +122 -0
- package/src/providers/spec/fsm-driver.ts +5 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ChatMessage } from './types.js';
|
|
7
7
|
import type { InteractivePrompt, InteractivePromptResponse } from './providers/types/interactive-prompt.js';
|
|
8
|
+
import type { MeshSendKeyItem, MeshSendKeyName } from './cli-adapters/provider-cli-shared.js';
|
|
8
9
|
export interface CliAdapterStatus {
|
|
9
10
|
status?: string;
|
|
10
11
|
parsedStatus?: string;
|
|
@@ -118,6 +119,34 @@ export interface CliAdapter {
|
|
|
118
119
|
cancel(): void;
|
|
119
120
|
isProcessing(): boolean;
|
|
120
121
|
isReady(): boolean;
|
|
122
|
+
isAlive?(): boolean;
|
|
123
|
+
getTerminalScreenSnapshot?(maxBytes?: number): {
|
|
124
|
+
text: string;
|
|
125
|
+
cursor: {
|
|
126
|
+
col: number;
|
|
127
|
+
row: number;
|
|
128
|
+
};
|
|
129
|
+
cols: number;
|
|
130
|
+
rows: number;
|
|
131
|
+
truncated: boolean;
|
|
132
|
+
originalBytes: number;
|
|
133
|
+
returnedBytes: number;
|
|
134
|
+
hash: string;
|
|
135
|
+
};
|
|
136
|
+
injectKeys?(items: MeshSendKeyItem[], opts?: {
|
|
137
|
+
allowModalOverride?: boolean;
|
|
138
|
+
}): Promise<{
|
|
139
|
+
ok: true;
|
|
140
|
+
keys: MeshSendKeyName[];
|
|
141
|
+
hasDestructive: boolean;
|
|
142
|
+
submits: boolean;
|
|
143
|
+
bytes: number;
|
|
144
|
+
} | {
|
|
145
|
+
ok: false;
|
|
146
|
+
refused: 'submit_race' | 'actionable_modal';
|
|
147
|
+
keys: MeshSendKeyName[];
|
|
148
|
+
hasDestructive: boolean;
|
|
149
|
+
}>;
|
|
121
150
|
setOnStatusChange(callback: () => void): void;
|
|
122
151
|
updateRuntimeSettings?(settings: Record<string, unknown>): void;
|
|
123
152
|
setCliScripts?(scripts: Record<string, unknown>): void;
|
package/dist/index.js
CHANGED
|
@@ -428,10 +428,10 @@ function readInjected(value) {
|
|
|
428
428
|
}
|
|
429
429
|
function getDaemonBuildInfo() {
|
|
430
430
|
if (cached) return cached;
|
|
431
|
-
const commit = readInjected(true ? "
|
|
432
|
-
const commitShort = readInjected(true ? "
|
|
433
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
434
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
431
|
+
const commit = readInjected(true ? "23529650799df69f9a14f16f6c2b34db153f1b27" : void 0) ?? "unknown";
|
|
432
|
+
const commitShort = readInjected(true ? "23529650" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
|
|
433
|
+
const version = readInjected(true ? "0.9.82-rc.554" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
|
|
434
|
+
const builtAt = readInjected(true ? "2026-07-17T07:57:39.144Z" : void 0);
|
|
435
435
|
cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
|
|
436
436
|
return cached;
|
|
437
437
|
}
|
|
@@ -43722,6 +43722,12 @@ var TerminalAdapter = class {
|
|
|
43722
43722
|
const pos = this.screen.getCursorPosition();
|
|
43723
43723
|
return { row: pos.row, col: pos.col };
|
|
43724
43724
|
}
|
|
43725
|
+
/** Current terminal geometry (columns × rows). Tracked here rather than
|
|
43726
|
+
* read off the screen buffer so a resize is reflected immediately, before
|
|
43727
|
+
* the next repaint. Consumed by the mesh_read_terminal viewport read. */
|
|
43728
|
+
getScreenSize() {
|
|
43729
|
+
return { cols: this.cols, rows: this.rows };
|
|
43730
|
+
}
|
|
43725
43731
|
send_keys(text) {
|
|
43726
43732
|
this.recordEvent("input", capPreview(escapeControl(text)), text.length);
|
|
43727
43733
|
this.pty?.write(text);
|
|
@@ -44060,6 +44066,9 @@ var FsmDriver = class {
|
|
|
44060
44066
|
getScreen() {
|
|
44061
44067
|
return this.adapter.snapshot();
|
|
44062
44068
|
}
|
|
44069
|
+
getScreenSize() {
|
|
44070
|
+
return this.adapter.getScreenSize();
|
|
44071
|
+
}
|
|
44063
44072
|
/** Scrollback-inclusive screen as line array — used only for modal/button
|
|
44064
44073
|
* content extraction so a tall prompt's off-screen anchors stay matchable.
|
|
44065
44074
|
* Falls back to the viewport snapshot if scrollback read is unavailable. */
|
|
@@ -46098,6 +46107,8 @@ function readTailJsonlLines(filePath, maxBytes) {
|
|
|
46098
46107
|
|
|
46099
46108
|
// src/providers/spec/cli-adapter.ts
|
|
46100
46109
|
var fs20 = __toESM(require("fs"));
|
|
46110
|
+
var import_node_crypto4 = require("crypto");
|
|
46111
|
+
init_provider_cli_shared();
|
|
46101
46112
|
init_logger();
|
|
46102
46113
|
function stripAnsi3(text) {
|
|
46103
46114
|
return String(text || "").replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
|
|
@@ -46276,6 +46287,105 @@ var SpecCliAdapter = class _SpecCliAdapter {
|
|
|
46276
46287
|
isReady() {
|
|
46277
46288
|
return this.spawned && !this.exited;
|
|
46278
46289
|
}
|
|
46290
|
+
// Process liveness for the MESH-STALL-WATCH watchdog (checkMeshWorkerStall).
|
|
46291
|
+
// The spec path drives the child through the transport/driver rather than a
|
|
46292
|
+
// directly-held ptyProcess handle, so liveness is tracked by the spawned/exited
|
|
46293
|
+
// lifecycle flags — the same pair isReady() uses. A spawned, not-yet-exited
|
|
46294
|
+
// session is alive. ProviderCliAdapter exposes the equivalent via `ptyProcess !== null`.
|
|
46295
|
+
isAlive() {
|
|
46296
|
+
return this.spawned && !this.exited;
|
|
46297
|
+
}
|
|
46298
|
+
// MESH-READ-TERMINAL / MESH-SEND-KEYS byte caps — same envelope as
|
|
46299
|
+
// ProviderCliAdapter (32KiB default view, 64KiB absolute hard cap). Bytes,
|
|
46300
|
+
// not chars: a multi-byte-glyph screen can exceed an MCP payload cap while
|
|
46301
|
+
// the char count still looks safe.
|
|
46302
|
+
static TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES = 32 * 1024;
|
|
46303
|
+
static TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES = 64 * 1024;
|
|
46304
|
+
/**
|
|
46305
|
+
* MESH-READ-TERMINAL (feature 2: RAW terminal read). Least-privilege read
|
|
46306
|
+
* of the CURRENT rendered viewport for mesh_read_terminal on the spec path
|
|
46307
|
+
* (claude-cli / antigravity / codex-cli — the native-source providers that
|
|
46308
|
+
* route through SpecCliAdapter). Mirrors ProviderCliAdapter.getTerminalScreenSnapshot:
|
|
46309
|
+
* - returns ONLY the driver's current viewport snapshot, the cursor
|
|
46310
|
+
* position and the terminal geometry — NO scrollback, NO parser/FSM
|
|
46311
|
+
* state, NO debug buffers;
|
|
46312
|
+
* - the payload is byte-bounded (UTF-8) with bottom-tail preservation so a
|
|
46313
|
+
* screen of multi-byte glyphs can never exceed the MCP payload cap;
|
|
46314
|
+
* - `hash` is over the FULL untruncated viewport so a caller can detect a
|
|
46315
|
+
* screen change across polls even when the returned text was truncated.
|
|
46316
|
+
*
|
|
46317
|
+
* SECURITY: the raw viewport can carry tokens / command args / env / user
|
|
46318
|
+
* data. Callers MUST gate this on mesh ownership and MUST NOT log the text.
|
|
46319
|
+
*/
|
|
46320
|
+
getTerminalScreenSnapshot(maxBytes = _SpecCliAdapter.TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES) {
|
|
46321
|
+
const cap = Math.min(
|
|
46322
|
+
_SpecCliAdapter.TERMINAL_SNAPSHOT_ABSOLUTE_MAX_BYTES,
|
|
46323
|
+
Math.max(1024, Math.floor(maxBytes) || _SpecCliAdapter.TERMINAL_SNAPSHOT_DEFAULT_MAX_BYTES)
|
|
46324
|
+
);
|
|
46325
|
+
let rawViewport = "";
|
|
46326
|
+
try {
|
|
46327
|
+
rawViewport = this.driver.snapshot() || "";
|
|
46328
|
+
} catch {
|
|
46329
|
+
rawViewport = "";
|
|
46330
|
+
}
|
|
46331
|
+
let cursor = { row: 0, col: 0 };
|
|
46332
|
+
try {
|
|
46333
|
+
cursor = this.driver.getCursorPosition();
|
|
46334
|
+
} catch {
|
|
46335
|
+
}
|
|
46336
|
+
let size = { cols: 0, rows: 0 };
|
|
46337
|
+
try {
|
|
46338
|
+
size = this.driver.getScreenSize?.() ?? size;
|
|
46339
|
+
} catch {
|
|
46340
|
+
}
|
|
46341
|
+
const truncation = truncateToByteTailByLine(rawViewport, cap);
|
|
46342
|
+
const hash = (0, import_node_crypto4.createHash)("sha256").update(rawViewport, "utf8").digest("hex").slice(0, 16);
|
|
46343
|
+
return {
|
|
46344
|
+
text: truncation.text,
|
|
46345
|
+
cursor: { col: cursor.col, row: cursor.row },
|
|
46346
|
+
cols: size.cols,
|
|
46347
|
+
rows: size.rows,
|
|
46348
|
+
truncated: truncation.truncated,
|
|
46349
|
+
originalBytes: truncation.originalBytes,
|
|
46350
|
+
returnedBytes: truncation.returnedBytes,
|
|
46351
|
+
hash
|
|
46352
|
+
};
|
|
46353
|
+
}
|
|
46354
|
+
/**
|
|
46355
|
+
* MESH-SEND-KEYS (feature 3: key injection). Inject a STRUCTURED key
|
|
46356
|
+
* sequence into the spec-driven PTY for mesh_send_keys. Mirrors
|
|
46357
|
+
* ProviderCliAdapter.injectKeys' modal fail-closed guard, then writes the
|
|
46358
|
+
* whole encoded sequence in ONE pty_write dispatch (text+ENTER is a single
|
|
46359
|
+
* contiguous string, so a submit key can never be separated from the text
|
|
46360
|
+
* it submits).
|
|
46361
|
+
*
|
|
46362
|
+
* The spec path drives the child through the FsmDriver, not a directly-held
|
|
46363
|
+
* ptyProcess — there is no adapter-level echo-gate/submit-retry FIFO to race
|
|
46364
|
+
* against here (the driver serializes its own writes), so the only guard is
|
|
46365
|
+
* the modal fail-closed: a NON-destructive injection into an actionable
|
|
46366
|
+
* approval modal is refused (use mesh_approve) unless explicitly overridden.
|
|
46367
|
+
* A destructive ESC/CTRL_C dismisses rather than confirms, so it is allowed
|
|
46368
|
+
* past this gate (the tool layer owns the destructive double-gate + audit).
|
|
46369
|
+
* This method NEVER logs the literal text — only key enums / byte length.
|
|
46370
|
+
*/
|
|
46371
|
+
async injectKeys(items, opts = {}) {
|
|
46372
|
+
if (!this.spawned || this.exited) throw new Error(`${this.cliName} is not running`);
|
|
46373
|
+
const encoded = encodeMeshSendKeys(items);
|
|
46374
|
+
const modalActive = this.latestState?.status === "approval";
|
|
46375
|
+
if (modalActive && !encoded.hasDestructive && !opts.allowModalOverride) {
|
|
46376
|
+
LOG.warn("SpecAdapter", `[${this.cliType}] send_keys refused (actionable_modal): keys=${encoded.keys.join(",")} \u2014 use mesh_approve`);
|
|
46377
|
+
return { ok: false, refused: "actionable_modal", keys: encoded.keys, hasDestructive: encoded.hasDestructive };
|
|
46378
|
+
}
|
|
46379
|
+
this.driver.dispatch({ kind: "pty_write", data: encoded.sequence });
|
|
46380
|
+
LOG.info("SpecAdapter", `[${this.cliType}] send_keys injected keys=${encoded.keys.join(",") || "(text-only)"} bytes=${Buffer.byteLength(encoded.sequence, "utf8")} destructive=${encoded.hasDestructive}`);
|
|
46381
|
+
return {
|
|
46382
|
+
ok: true,
|
|
46383
|
+
keys: encoded.keys,
|
|
46384
|
+
hasDestructive: encoded.hasDestructive,
|
|
46385
|
+
submits: encoded.submits,
|
|
46386
|
+
bytes: Buffer.byteLength(encoded.sequence, "utf8")
|
|
46387
|
+
};
|
|
46388
|
+
}
|
|
46279
46389
|
setOnStatusChange(cb) {
|
|
46280
46390
|
this.statusCallback = cb;
|
|
46281
46391
|
}
|
|
@@ -48543,6 +48653,25 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
48543
48653
|
}
|
|
48544
48654
|
return "";
|
|
48545
48655
|
}
|
|
48656
|
+
/**
|
|
48657
|
+
* NOTIF Defect-B: the final assistant summary this instance ALREADY parsed and
|
|
48658
|
+
* cached for the current turn (lastCompletionSummary), if any. The evidence
|
|
48659
|
+
* probe (completionFinalAssistantEvidence) is a POINT-SAMPLE: on a native-source
|
|
48660
|
+
* provider (antigravity) the parsed screen and the native transcript can both
|
|
48661
|
+
* momentarily yield no in-turn final assistant at the exact instant the
|
|
48662
|
+
* completion gate fires — source='unavailable', missingEvidence=true — even
|
|
48663
|
+
* though a prior poll already read the real answer off native-history and cached
|
|
48664
|
+
* it here (the same value mesh_read_chat.summary shows). Consulting the cache at
|
|
48665
|
+
* emit time lets that already-secured summary count as evidence, so the completion
|
|
48666
|
+
* notification carries the answer instead of completion_diagnostic=missing_final_assistant
|
|
48667
|
+
* with an empty summary. Returns '' when the cache is empty or was reset by the
|
|
48668
|
+
* next turn (see lastCompletionSummary = null on onTurnStarted).
|
|
48669
|
+
*/
|
|
48670
|
+
cachedCompletionSummaryContent() {
|
|
48671
|
+
const cached3 = this.lastCompletionSummary;
|
|
48672
|
+
const content = typeof cached3?.content === "string" ? cached3.content.trim() : "";
|
|
48673
|
+
return content;
|
|
48674
|
+
}
|
|
48546
48675
|
completionFinalAssistantEvidence(parsedMessages, turnStartedAt) {
|
|
48547
48676
|
const turnClosed = !this.hasAdapterPendingResponse();
|
|
48548
48677
|
if (this.completionHasFinalAssistantMessage(parsedMessages, turnStartedAt)) {
|
|
@@ -48606,12 +48735,19 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
48606
48735
|
const lastVisibleRole = typeof lastVisible?.role === "string" ? lastVisible.role.trim().toLowerCase() : null;
|
|
48607
48736
|
const lastVisibleKind = typeof lastVisible?.kind === "string" ? lastVisible.kind : null;
|
|
48608
48737
|
const lastVisibleContentLength = lastVisible ? flattenContent(lastVisible.content).trim().length : 0;
|
|
48738
|
+
const cachedSummary = evidence.present ? "" : this.cachedCompletionSummaryContent();
|
|
48739
|
+
const creditedFromCache = !evidence.present && cachedSummary.length > 0;
|
|
48740
|
+
const finalAssistantPresent = evidence.present || creditedFromCache;
|
|
48741
|
+
const finalAssistantEvidenceSource = evidence.present ? evidence.source : creditedFromCache ? "cached-summary" : evidence.source;
|
|
48742
|
+
const clearMissingBlock = creditedFromCache && args.blockReason === "missing_final_assistant";
|
|
48743
|
+
const effectiveBlockReason = clearMissingBlock ? void 0 : args.blockReason;
|
|
48609
48744
|
return {
|
|
48610
48745
|
providerType: this.type,
|
|
48611
48746
|
sessionId: this.instanceId,
|
|
48612
48747
|
providerSessionId: this.providerSessionId || null,
|
|
48613
48748
|
workspace: this.workingDir,
|
|
48614
|
-
blockReason:
|
|
48749
|
+
...effectiveBlockReason ? { blockReason: effectiveBlockReason } : {},
|
|
48750
|
+
...clearMissingBlock ? { originalBlockReason: args.blockReason } : {},
|
|
48615
48751
|
emittedAfterFinalizationTimeout: args.emittedAfterFinalizationTimeout,
|
|
48616
48752
|
waitedMs: args.waitedMs,
|
|
48617
48753
|
maxWaitMs: COMPLETED_FINALIZATION_MAX_WAIT_MS,
|
|
@@ -48619,8 +48755,9 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
48619
48755
|
latestVisibleStatus: args.latestVisibleStatus,
|
|
48620
48756
|
parsedStatus: typeof parsed?.status === "string" ? parsed.status : parseError ? "parse_error" : "unknown",
|
|
48621
48757
|
parseError: parseError || void 0,
|
|
48622
|
-
finalAssistantPresent
|
|
48623
|
-
|
|
48758
|
+
finalAssistantPresent,
|
|
48759
|
+
finalAssistantFromCachedSummary: !evidence.present && cachedSummary.length > 0,
|
|
48760
|
+
finalAssistantEvidenceSource,
|
|
48624
48761
|
visibleMessageCount: visibleMessages.length,
|
|
48625
48762
|
lastVisibleRole,
|
|
48626
48763
|
lastVisibleKind,
|
|
@@ -48814,6 +48951,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
48814
48951
|
*/
|
|
48815
48952
|
getTerminalScreenSnapshot(maxBytes) {
|
|
48816
48953
|
if (!this.isMeshWorkerSession()) return null;
|
|
48954
|
+
if (typeof this.adapter.getTerminalScreenSnapshot !== "function") return null;
|
|
48817
48955
|
return this.adapter.getTerminalScreenSnapshot(maxBytes);
|
|
48818
48956
|
}
|
|
48819
48957
|
/**
|
|
@@ -48832,6 +48970,9 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
48832
48970
|
if (!this.isMeshWorkerSession()) {
|
|
48833
48971
|
return { ok: false, refused: "not_mesh_worker", keys: [], hasDestructive: false };
|
|
48834
48972
|
}
|
|
48973
|
+
if (typeof this.adapter.injectKeys !== "function") {
|
|
48974
|
+
return { ok: false, refused: "unsupported", keys: [], hasDestructive: false };
|
|
48975
|
+
}
|
|
48835
48976
|
return this.adapter.injectKeys(items, opts);
|
|
48836
48977
|
}
|
|
48837
48978
|
/**
|
|
@@ -48864,7 +49005,7 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
48864
49005
|
this.meshStallEmittedForAnchor = false;
|
|
48865
49006
|
return;
|
|
48866
49007
|
}
|
|
48867
|
-
if (!this.adapter.isAlive()) {
|
|
49008
|
+
if (typeof this.adapter.isAlive === "function" && !this.adapter.isAlive()) {
|
|
48868
49009
|
this.meshStallAnchorAt = -1;
|
|
48869
49010
|
this.meshStallEmittedForAnchor = false;
|
|
48870
49011
|
return;
|
|
@@ -49221,7 +49362,13 @@ var CliProviderInstance = class _CliProviderInstance {
|
|
|
49221
49362
|
// delegated session's inbox preview blank — or, for a LOCAL worktree session,
|
|
49222
49363
|
// stuck on the dispatched user task. If the parser DID surface assistant text,
|
|
49223
49364
|
// prefer it; only fall back to '' when no assistant summary can be derived.
|
|
49224
|
-
|
|
49365
|
+
// NOTIF Defect-B: completionFinalSummary is a point-sample of native-history/
|
|
49366
|
+
// screen at THIS instant; on a native-source provider (antigravity) it can be
|
|
49367
|
+
// empty at the forced-emit instant even though a prior poll already cached the
|
|
49368
|
+
// real answer (lastCompletionSummary). Fall back to the cache so the notification
|
|
49369
|
+
// carries the summary that mesh_read_chat.summary already shows — consistent with
|
|
49370
|
+
// completionDiagnostic.finalAssistantPresent being credited from the same cache.
|
|
49371
|
+
finalSummary: this.completionFinalSummary(this.adapter?.getScriptParsedStatus()?.messages, pending.turnStartedAt) || this.cachedCompletionSummaryContent() || (blockReason.startsWith("parsed_status:") ? "" : void 0),
|
|
49225
49372
|
completionDiagnostic
|
|
49226
49373
|
});
|
|
49227
49374
|
this.completedDebouncePending = null;
|
|
@@ -60917,6 +61064,150 @@ ${e?.stderr || ""}`
|
|
|
60917
61064
|
};
|
|
60918
61065
|
}
|
|
60919
61066
|
}
|
|
61067
|
+
async function classifyPatchEquivalenceFailure(repoRoot, baseHead, branchHead, summary, options = {}) {
|
|
61068
|
+
const targetBaseRef = options.targetBaseRef || baseHead;
|
|
61069
|
+
const autoPublish = options.autoPublishSubmoduleMainCommits;
|
|
61070
|
+
const evidence = {
|
|
61071
|
+
baseHead,
|
|
61072
|
+
branchHead,
|
|
61073
|
+
mergeBase: summary.mergeBase,
|
|
61074
|
+
expectedPatchId: summary.expectedPatchId,
|
|
61075
|
+
actualPatchId: summary.actualPatchId,
|
|
61076
|
+
patchIdEqual: !!summary.expectedPatchId && summary.expectedPatchId === summary.actualPatchId,
|
|
61077
|
+
...autoPublish !== void 0 ? { autoPublishSubmoduleMainCommits: autoPublish } : {}
|
|
61078
|
+
};
|
|
61079
|
+
try {
|
|
61080
|
+
const git = (args) => (0, import_node_child_process6.execFileSync)(GIT2, args, {
|
|
61081
|
+
cwd: repoRoot,
|
|
61082
|
+
encoding: "utf8",
|
|
61083
|
+
maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES,
|
|
61084
|
+
windowsHide: true
|
|
61085
|
+
});
|
|
61086
|
+
const gitOk = (args) => {
|
|
61087
|
+
try {
|
|
61088
|
+
git(args);
|
|
61089
|
+
return true;
|
|
61090
|
+
} catch {
|
|
61091
|
+
return false;
|
|
61092
|
+
}
|
|
61093
|
+
};
|
|
61094
|
+
let ahead = 0;
|
|
61095
|
+
let behind = 0;
|
|
61096
|
+
try {
|
|
61097
|
+
const out = git(["rev-list", "--left-right", "--count", `${targetBaseRef}...${branchHead}`]).trim();
|
|
61098
|
+
const [left, right] = out.split(/\s+/).map((n) => Number.parseInt(n, 10));
|
|
61099
|
+
behind = Number.isFinite(left) ? left : 0;
|
|
61100
|
+
ahead = Number.isFinite(right) ? right : 0;
|
|
61101
|
+
} catch {
|
|
61102
|
+
}
|
|
61103
|
+
evidence.ahead = ahead;
|
|
61104
|
+
evidence.behind = behind;
|
|
61105
|
+
const baseIsAncestor = gitOk(["merge-base", "--is-ancestor", targetBaseRef, branchHead]);
|
|
61106
|
+
evidence.baseDiverged = !baseIsAncestor;
|
|
61107
|
+
let diffStat = "";
|
|
61108
|
+
try {
|
|
61109
|
+
if (summary.mergedTree) {
|
|
61110
|
+
diffStat = git(["diff", "--stat", baseHead, summary.mergedTree]).trim().split("\n").filter(Boolean).slice(-1)[0] || "";
|
|
61111
|
+
} else {
|
|
61112
|
+
diffStat = git(["diff", "--stat", baseHead, branchHead]).trim().split("\n").filter(Boolean).slice(-1)[0] || "";
|
|
61113
|
+
}
|
|
61114
|
+
} catch {
|
|
61115
|
+
}
|
|
61116
|
+
if (diffStat) evidence.diffStat = diffStat;
|
|
61117
|
+
const submoduleGitlinks = [];
|
|
61118
|
+
try {
|
|
61119
|
+
const nameStatus = git(["diff", "--name-only", "--diff-filter=d", baseHead, branchHead]).trim();
|
|
61120
|
+
const changedPaths = nameStatus ? nameStatus.split("\n").map((p) => p.trim()).filter(Boolean) : [];
|
|
61121
|
+
for (const p of changedPaths) {
|
|
61122
|
+
let baseCommit;
|
|
61123
|
+
let branchCommit;
|
|
61124
|
+
try {
|
|
61125
|
+
const baseLs = git(["ls-tree", baseHead, "--", p]).trim();
|
|
61126
|
+
const branchLs = git(["ls-tree", branchHead, "--", p]).trim();
|
|
61127
|
+
const isGitlink = /(^|\s)160000\s/.test(baseLs) || /(^|\s)160000\s/.test(branchLs);
|
|
61128
|
+
if (!isGitlink) continue;
|
|
61129
|
+
baseCommit = baseLs.split(/\s+/)[2];
|
|
61130
|
+
branchCommit = branchLs.split(/\s+/)[2];
|
|
61131
|
+
} catch {
|
|
61132
|
+
continue;
|
|
61133
|
+
}
|
|
61134
|
+
const submoduleRepo = (0, import_path14.join)(repoRoot, p);
|
|
61135
|
+
let fastForward;
|
|
61136
|
+
let reachableFromOriginMain;
|
|
61137
|
+
if (branchCommit) {
|
|
61138
|
+
if (baseCommit) {
|
|
61139
|
+
fastForward = execGitOk(submoduleRepo, ["merge-base", "--is-ancestor", baseCommit, branchCommit]);
|
|
61140
|
+
}
|
|
61141
|
+
reachableFromOriginMain = execGitOk(submoduleRepo, ["merge-base", "--is-ancestor", branchCommit, "refs/remotes/origin/main"]);
|
|
61142
|
+
}
|
|
61143
|
+
submoduleGitlinks.push({ path: p, baseCommit, branchCommit, fastForward, reachableFromOriginMain });
|
|
61144
|
+
}
|
|
61145
|
+
} catch {
|
|
61146
|
+
}
|
|
61147
|
+
if (submoduleGitlinks.length) evidence.submoduleGitlinks = submoduleGitlinks;
|
|
61148
|
+
const gitlinkFf = summary.gitlinkTrivialFastForward;
|
|
61149
|
+
const noResidualDiff = !evidence.diffStat && (!summary.actualPatchId || summary.actualPatchId === "");
|
|
61150
|
+
if (ahead === 0 && behind === 0 && noResidualDiff) {
|
|
61151
|
+
return {
|
|
61152
|
+
detailedReason: "already_converged",
|
|
61153
|
+
detailedReasonDescription: "Branch is already identical to the target base (ahead 0, behind 0, no residual diff); the merge would be a no-op.",
|
|
61154
|
+
recommendedAction: "Treat as already converged \u2014 no merge needed. Verify with `git range-diff` / patch-id, then mark the branch merged (or clean up the worktree).",
|
|
61155
|
+
evidence
|
|
61156
|
+
};
|
|
61157
|
+
}
|
|
61158
|
+
const unreachable = submoduleGitlinks.filter((g) => g.reachableFromOriginMain === false);
|
|
61159
|
+
if (unreachable.length > 0) {
|
|
61160
|
+
const paths = unreachable.map((g) => g.path).join(", ");
|
|
61161
|
+
return {
|
|
61162
|
+
detailedReason: "submodule_unreachable",
|
|
61163
|
+
detailedReasonDescription: `Submodule gitlink commit(s) not reachable from submodule origin/main (publish needed): ${paths}.`,
|
|
61164
|
+
recommendedAction: `Publish the submodule commit(s) to submodule origin/main, then retry mesh_refine_node (policy allowAutoPublishSubmoduleMainCommits=${autoPublish === void 0 ? "unknown" : autoPublish}).`,
|
|
61165
|
+
evidence
|
|
61166
|
+
};
|
|
61167
|
+
}
|
|
61168
|
+
const changedGitlinks = submoduleGitlinks.length > 0;
|
|
61169
|
+
const allGitlinksFf = changedGitlinks && submoduleGitlinks.every((g) => g.fastForward === true);
|
|
61170
|
+
const gateSawUnresolvedGitlinkFf = gitlinkFf?.resolved === false && Array.isArray(gitlinkFf.gitlinks) && gitlinkFf.gitlinks.some((g) => g.fastForward);
|
|
61171
|
+
if (baseIsAncestor && (evidence.patchIdEqual || allGitlinksFf || gateSawUnresolvedGitlinkFf)) {
|
|
61172
|
+
return {
|
|
61173
|
+
detailedReason: "trivial_ff_misjudgment",
|
|
61174
|
+
detailedReasonDescription: "HEAD descends the target base and the patch content matches; the block is a submodule gitlink trivial fast-forward that merge-tree refused, not a real divergence.",
|
|
61175
|
+
recommendedAction: "Converge via the strict fast-forward-only bypass (verify HEAD descends origin/main and patch-id equality, then merge --ff-only) instead of the refine gate.",
|
|
61176
|
+
evidence
|
|
61177
|
+
};
|
|
61178
|
+
}
|
|
61179
|
+
if (!baseIsAncestor) {
|
|
61180
|
+
return {
|
|
61181
|
+
detailedReason: "base_divergence",
|
|
61182
|
+
detailedReasonDescription: `Worktree base has diverged from ${targetBaseRef} (HEAD is not a descendant; ahead ${ahead}, behind ${behind}).`,
|
|
61183
|
+
recommendedAction: `Rebase the branch onto ${targetBaseRef}, then retry mesh_refine_node.`,
|
|
61184
|
+
evidence
|
|
61185
|
+
};
|
|
61186
|
+
}
|
|
61187
|
+
return {
|
|
61188
|
+
detailedReason: "actual_patch_diff",
|
|
61189
|
+
detailedReasonDescription: "The merge introduces content not equivalent to the branch's cumulative patch (expected tree vs actual merge diff differ).",
|
|
61190
|
+
recommendedAction: "Manual review required \u2014 inspect the residual diff; the branch content is not patch-equivalent to a clean merge onto the base.",
|
|
61191
|
+
evidence
|
|
61192
|
+
};
|
|
61193
|
+
} catch (e) {
|
|
61194
|
+
evidence.classifierError = e?.message || String(e);
|
|
61195
|
+
return {
|
|
61196
|
+
detailedReason: "unclassified",
|
|
61197
|
+
detailedReasonDescription: "Patch-equivalence sub-cause could not be classified (git inspection failed); see classifierError.",
|
|
61198
|
+
recommendedAction: "Inspect the refineStages and patchEquivalence summary manually to determine the cause.",
|
|
61199
|
+
evidence
|
|
61200
|
+
};
|
|
61201
|
+
}
|
|
61202
|
+
}
|
|
61203
|
+
function execGitOk(cwd, args) {
|
|
61204
|
+
try {
|
|
61205
|
+
(0, import_node_child_process6.execFileSync)(GIT2, args, { cwd, encoding: "utf8", maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES, windowsHide: true });
|
|
61206
|
+
return true;
|
|
61207
|
+
} catch {
|
|
61208
|
+
return false;
|
|
61209
|
+
}
|
|
61210
|
+
}
|
|
60920
61211
|
async function checkWorktreeChangesPatchEquivalentInRef(repoRoot, ref, worktreeHead) {
|
|
60921
61212
|
const startedAt = Date.now();
|
|
60922
61213
|
try {
|
|
@@ -62236,9 +62527,23 @@ async function refineSyncBaseStage(self, ctx) {
|
|
|
62236
62527
|
error: submoduleHintPatchEquivalence.error,
|
|
62237
62528
|
actionableHint: submoduleHintPatchEquivalence.actionableHint
|
|
62238
62529
|
});
|
|
62530
|
+
const classification = await classifyPatchEquivalenceFailure(
|
|
62531
|
+
repoRoot,
|
|
62532
|
+
baseHead,
|
|
62533
|
+
ctx.branchHead,
|
|
62534
|
+
submoduleHintPatchEquivalence,
|
|
62535
|
+
{
|
|
62536
|
+
targetBaseRef: baseHead,
|
|
62537
|
+
autoPublishSubmoduleMainCommits: resolveRefineryAutoPublishSubmoduleMainCommits(ctx.mesh, node.workspace).enabled
|
|
62538
|
+
}
|
|
62539
|
+
);
|
|
62239
62540
|
return { kind: "terminal", result: {
|
|
62240
62541
|
success: false,
|
|
62241
62542
|
code: "patch_equivalence_failed",
|
|
62543
|
+
detailedReason: classification.detailedReason,
|
|
62544
|
+
detailedReasonDescription: classification.detailedReasonDescription,
|
|
62545
|
+
recommendedAction: classification.recommendedAction,
|
|
62546
|
+
evidence: classification.evidence,
|
|
62242
62547
|
convergenceStatus: "blocked_review",
|
|
62243
62548
|
error: "Refinery patch-equivalence preflight failed (submodule gitlink conflict); merge/refine was not attempted.",
|
|
62244
62549
|
branch,
|
|
@@ -62378,7 +62683,7 @@ ${tail}` : ""
|
|
|
62378
62683
|
return { kind: "continue", ctx };
|
|
62379
62684
|
}
|
|
62380
62685
|
async function refinePatchEquivalenceStage(self, ctx) {
|
|
62381
|
-
const { meshId, nodeId, args, repoRoot, baseHead, branch, baseBranch, validationSummary, refineStages } = ctx;
|
|
62686
|
+
const { meshId, nodeId, args, repoRoot, baseHead, branch, baseBranch, mesh, node, validationSummary, refineStages } = ctx;
|
|
62382
62687
|
const branchHead = ctx.branchHead;
|
|
62383
62688
|
const patchEquivalenceStarted = Date.now();
|
|
62384
62689
|
const patchEquivalence = await runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead);
|
|
@@ -62392,9 +62697,27 @@ async function refinePatchEquivalenceStage(self, ctx) {
|
|
|
62392
62697
|
if (!patchEquivalence.equivalent) {
|
|
62393
62698
|
const alreadyMergedViaOtherPath = !patchEquivalence.actualPatchId && !!patchEquivalence.expectedPatchId;
|
|
62394
62699
|
if (!alreadyMergedViaOtherPath) {
|
|
62700
|
+
const classification = await classifyPatchEquivalenceFailure(
|
|
62701
|
+
repoRoot,
|
|
62702
|
+
baseHead,
|
|
62703
|
+
branchHead,
|
|
62704
|
+
patchEquivalence,
|
|
62705
|
+
{
|
|
62706
|
+
targetBaseRef: baseHead,
|
|
62707
|
+
autoPublishSubmoduleMainCommits: resolveRefineryAutoPublishSubmoduleMainCommits(mesh, node.workspace).enabled
|
|
62708
|
+
}
|
|
62709
|
+
);
|
|
62710
|
+
recordMeshRefineStage(refineStages, "patch_equivalence_classification", "failed", patchEquivalenceStarted, {
|
|
62711
|
+
detailedReason: classification.detailedReason,
|
|
62712
|
+
recommendedAction: classification.recommendedAction
|
|
62713
|
+
});
|
|
62395
62714
|
return { kind: "terminal", result: {
|
|
62396
62715
|
success: false,
|
|
62397
62716
|
code: "patch_equivalence_failed",
|
|
62717
|
+
detailedReason: classification.detailedReason,
|
|
62718
|
+
detailedReasonDescription: classification.detailedReasonDescription,
|
|
62719
|
+
recommendedAction: classification.recommendedAction,
|
|
62720
|
+
evidence: classification.evidence,
|
|
62398
62721
|
convergenceStatus: "blocked_review",
|
|
62399
62722
|
error: "Refinery patch-equivalence preflight failed; merge/refine was not attempted.",
|
|
62400
62723
|
branch,
|
|
@@ -63501,6 +63824,10 @@ async function finishMeshRefineJob(self, handle, args) {
|
|
|
63501
63824
|
};
|
|
63502
63825
|
if (typeof result.error === "string") ctx.error = result.error;
|
|
63503
63826
|
if (typeof result.blockedReason === "string") ctx.blockedReason = result.blockedReason;
|
|
63827
|
+
if (typeof result.detailedReason === "string") ctx.detailedReason = result.detailedReason;
|
|
63828
|
+
if (typeof result.detailedReasonDescription === "string") ctx.detailedReasonDescription = result.detailedReasonDescription;
|
|
63829
|
+
if (typeof result.recommendedAction === "string") ctx.recommendedAction = result.recommendedAction;
|
|
63830
|
+
if (result.evidence && typeof result.evidence === "object") ctx.evidence = result.evidence;
|
|
63504
63831
|
if (stage === "patch_equivalence" && result.patchEquivalence) {
|
|
63505
63832
|
const pe = result.patchEquivalence;
|
|
63506
63833
|
ctx.details = {
|
|
@@ -63508,7 +63835,10 @@ async function finishMeshRefineJob(self, handle, args) {
|
|
|
63508
63835
|
actualPatchId: pe.actualPatchId,
|
|
63509
63836
|
status: pe.status,
|
|
63510
63837
|
actionableHint: pe.actionableHint,
|
|
63511
|
-
error: pe.error
|
|
63838
|
+
error: pe.error,
|
|
63839
|
+
...typeof result.detailedReason === "string" ? { detailedReason: result.detailedReason } : {},
|
|
63840
|
+
...typeof result.recommendedAction === "string" ? { recommendedAction: result.recommendedAction } : {},
|
|
63841
|
+
...result.evidence && typeof result.evidence === "object" ? { evidence: result.evidence } : {}
|
|
63512
63842
|
};
|
|
63513
63843
|
}
|
|
63514
63844
|
if (stage === "submodule_reachability" && Array.isArray(result.unreachableSubmoduleCommits)) {
|