@deksden-com/dd-flow-cli 0.9.0-beta.113 → 0.9.0-beta.115
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/CHANGELOG.md +12 -0
- package/dist/build-info.json +3 -3
- package/dist/services/code-checks.js +27 -0
- package/dist/services/hooks.js +7 -1
- package/dist/services/vnext-merge.js +4 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.115
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Freeze the reviewed source check profile for MERGE after verifying that it only adds aliases to the integration baseline; keep existing check policy immutable and verify the post-apply profile against that frozen source.
|
|
8
|
+
|
|
9
|
+
## 0.9.0-beta.114
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Route controlled `merge apply` and `merge repair` hooks to the MERGE request's integration workspace, preserving native cwd validation across AGY, OpenCode, and Droid.
|
|
14
|
+
|
|
3
15
|
## 0.9.0-beta.113
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
3
|
+
"cli_version": "0.9.0-beta.115",
|
|
4
|
+
"cli_commit": "c9825bc3272340c5583cab59ccdaab47c9968b10",
|
|
5
|
+
"built_at": "2026-09-27T17:57:38.414Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
7
|
"version": "4.1.1",
|
|
8
8
|
"commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
|
|
@@ -3,6 +3,7 @@ import { isGitlessWorkspace, ownedWorkspaceHookPath, projectServicePathFilter, w
|
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { isDeepStrictEqual } from "node:util";
|
|
6
7
|
import { parse } from "shell-quote";
|
|
7
8
|
import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
8
9
|
import { AppError } from "../shared/errors.js";
|
|
@@ -66,6 +67,32 @@ export function codeCheckProfileHash(workspaceRoot, checks = []) {
|
|
|
66
67
|
};
|
|
67
68
|
return crypto.createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
|
|
68
69
|
}
|
|
70
|
+
/** The integration target starts at its baseline profile; the reviewed source
|
|
71
|
+
* may add aliases, but must not change existing check policy or bindings. */
|
|
72
|
+
export function mergeCheckProfileHash(sourceWorkspace, targetWorkspace, checks) {
|
|
73
|
+
const source = readCodeCheckProfile(sourceWorkspace).profile;
|
|
74
|
+
const target = readCodeCheckProfile(targetWorkspace).profile;
|
|
75
|
+
if (!source && !target)
|
|
76
|
+
return null;
|
|
77
|
+
if (!source || !target)
|
|
78
|
+
throw new AppError("check_profile_drift", "Source and integration check profiles differ", 1);
|
|
79
|
+
const retained = structuredClone(source);
|
|
80
|
+
for (const field of ["aliases", "ports_by_alias", "inputs_by_alias"]) {
|
|
81
|
+
const old = target[field];
|
|
82
|
+
if (!old) {
|
|
83
|
+
delete retained[field];
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const current = source[field] ?? {};
|
|
87
|
+
retained[field] = Object.fromEntries(Object.keys(old).filter(key => key in current).map(key => [key, current[key]]));
|
|
88
|
+
}
|
|
89
|
+
const existingAliases = new Set(Object.keys(target.aliases));
|
|
90
|
+
const changedExistingMetadata = ["ports_by_alias", "inputs_by_alias"].some(field => Object.keys(source[field] ?? {}).some(alias => existingAliases.has(alias) && !(alias in (target[field] ?? {}))));
|
|
91
|
+
if (changedExistingMetadata || !isDeepStrictEqual(retained, target)) {
|
|
92
|
+
throw new AppError("check_profile_drift", "Source check profile changes existing integration policy", 1, { source_profile_hash: codeCheckProfileHash(sourceWorkspace, checks), target_profile_hash: codeCheckProfileHash(targetWorkspace, checks) });
|
|
93
|
+
}
|
|
94
|
+
return codeCheckProfileHash(sourceWorkspace, checks);
|
|
95
|
+
}
|
|
69
96
|
export function validateCodeCheckCommands(workspaceRoot, commands) {
|
|
70
97
|
const { profile, file } = readCodeCheckProfile(workspaceRoot);
|
|
71
98
|
return commands.map((command) => {
|
package/dist/services/hooks.js
CHANGED
|
@@ -676,6 +676,12 @@ function controlledLifecycleCwd(context, projectRoot, facts, providerSessionId,
|
|
|
676
676
|
const project = context.db.get("SELECT id FROM projects WHERE root = ?", [projectRoot]);
|
|
677
677
|
if (!project)
|
|
678
678
|
return projectRoot;
|
|
679
|
+
if (facts?.invocation.operation === "merge_apply" || facts?.invocation.operation === "merge_repair") {
|
|
680
|
+
const requestId = commandPosition(facts.invocation, 0);
|
|
681
|
+
const request = requestId && context.db.get("SELECT target_workspace FROM merge_requests WHERE project_id = ? AND merge_request_id = ?", [project.id, requestId]);
|
|
682
|
+
if (request)
|
|
683
|
+
return resolveProjectRoot(request.target_workspace);
|
|
684
|
+
}
|
|
679
685
|
let work;
|
|
680
686
|
if (facts?.workId) {
|
|
681
687
|
try {
|
|
@@ -718,7 +724,7 @@ function controlledLifecycleCwd(context, projectRoot, facts, providerSessionId,
|
|
|
718
724
|
}
|
|
719
725
|
if (facts?.stageName === "specify" || facts?.stageName === "protocolize")
|
|
720
726
|
return projectRoot;
|
|
721
|
-
if (facts?.stageName === "merge") {
|
|
727
|
+
if (facts?.stageName === "merge" || facts?.invocation.operation === "merge_apply" || facts?.invocation.operation === "merge_repair") {
|
|
722
728
|
const pending = context.db.all("SELECT target_workspace FROM merge_requests WHERE project_id = ? AND run_id = ? AND status IN ('dispatching','active','action_required') ORDER BY updated_at DESC", [project.id, run.id]);
|
|
723
729
|
const targets = [...new Set(pending.map(item => resolveProjectRoot(item.target_workspace)))];
|
|
724
730
|
if (targets.length > 1)
|
|
@@ -17,7 +17,7 @@ import { runtimeCommand, checkRetryArgs, recoveryEvidenceInput } from "./runtime
|
|
|
17
17
|
import { acceptStageReport, prepareStageReport, publishStageReport } from "./stage-report-renderer.js";
|
|
18
18
|
import { bindStageCoordinatorWork, createChildWork, failWork, finishFanInWork, finishWork, refreshRunWorkProjection, startStageCoordinatorWork } from "./work-registry.js";
|
|
19
19
|
import { addVnextCodeRepair } from "./vnext-code.js";
|
|
20
|
-
import { checkReceiptEvidenceValid, checkReceipts,
|
|
20
|
+
import { checkReceiptEvidenceValid, checkReceipts, effectiveCheckDeclarations, mergeCheckProfileHash, runCodeChecks, validateFailedCheckRetry, workspaceChangedPaths, workspaceFingerprint } from "./code-checks.js";
|
|
21
21
|
import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
|
|
22
22
|
import { ensureLaneWorkspace, prepareLaneWorkspace, heartbeatLaneLock, releaseLaneLock, waitAcquireLaneLock } from "./lanes.js";
|
|
23
23
|
import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
@@ -71,7 +71,8 @@ export function prepareVnextMergeRequest(context, input) {
|
|
|
71
71
|
const freezeFile = path.join(freezeRoot, "source-freeze.json");
|
|
72
72
|
const gateFile = path.join(freezeRoot, "merge-gate.json");
|
|
73
73
|
fs.mkdirSync(freezeRoot, { recursive: true });
|
|
74
|
-
|
|
74
|
+
const profileHash = mergeCheckProfileHash(run.workspace_root, targetWorkspace, effective);
|
|
75
|
+
freezeMergeGate(gateFile, { runId: run.id, protocols, checks: effective, acceptanceRefs, profileHash, now: context.now() });
|
|
75
76
|
let sourceCommit;
|
|
76
77
|
let sourcePaths;
|
|
77
78
|
if (fs.existsSync(freezeFile)) {
|
|
@@ -109,7 +110,7 @@ export function prepareVnextMergeRequest(context, input) {
|
|
|
109
110
|
return requestView(context, raced);
|
|
110
111
|
const child = createChildWork(context, { parentWorkId: root.work_id, stage: "merge", slug: "merge", task: `Integrate frozen source ${sourceCommit} into ${targetBranch}, resolve material conflicts, and preserve accepted behavior.`, launchPolicy: route === "server" ? "fresh_agent_required" : "reuse_allowed", resultSchema: "dd-flow/merge-result@1", payload: { kind: "merge", checks: semantic, protocol_ids: protocols } });
|
|
111
112
|
const requestId = nextMergeRequestId(context);
|
|
112
|
-
context.db.run("INSERT INTO merge_requests (merge_request_id, project_id, run_id, executor_work_id, protocol_ids_json, source_workspace, source_branch, source_commit, target_workspace, target_branch, enqueue_target_head, execution_target_head, integration_commit, execution_route, status, dispatch_owner, dispatch_lease_token, dispatch_lease_expires_at, lock_acquired_at, checkpoint, profile_hash, replacement_of_merge_request_id, adapter_receipt_json, result_json, last_error_json, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, 'queued', NULL, NULL, NULL, NULL, 'queued', ?, ?, NULL, NULL, NULL, ?, ?, NULL)", [requestId, project.id, run.id, child.work_id, JSON.stringify(protocols), run.workspace_root, sourceBranch, sourceCommit, targetWorkspace, targetBranch, enqueueTarget, route,
|
|
113
|
+
context.db.run("INSERT INTO merge_requests (merge_request_id, project_id, run_id, executor_work_id, protocol_ids_json, source_workspace, source_branch, source_commit, target_workspace, target_branch, enqueue_target_head, execution_target_head, integration_commit, execution_route, status, dispatch_owner, dispatch_lease_token, dispatch_lease_expires_at, lock_acquired_at, checkpoint, profile_hash, replacement_of_merge_request_id, adapter_receipt_json, result_json, last_error_json, created_at, updated_at, completed_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, 'queued', NULL, NULL, NULL, NULL, 'queued', ?, ?, NULL, NULL, NULL, ?, ?, NULL)", [requestId, project.id, run.id, child.work_id, JSON.stringify(protocols), run.workspace_root, sourceBranch, sourceCommit, targetWorkspace, targetBranch, enqueueTarget, route, profileHash, replacementOf(run), now, now]);
|
|
113
114
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "merge_request_queued", merge_request_id: requestId, work_id: child.work_id, route, source_commit: sourceCommit, target_branch: targetBranch });
|
|
114
115
|
return requestView(context, requireRequest(context, requestId));
|
|
115
116
|
});
|