@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 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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.113",
4
- "cli_commit": "97203d7f7def0313962f58ed3476cde9d9f57a74",
5
- "built_at": "2026-09-27T15:24:27.962Z",
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) => {
@@ -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, codeCheckProfileHash, effectiveCheckDeclarations, runCodeChecks, validateFailedCheckRetry, workspaceChangedPaths, workspaceFingerprint } from "./code-checks.js";
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
- freezeMergeGate(gateFile, { runId: run.id, protocols, checks: effective, acceptanceRefs, profileHash: codeCheckProfileHash(targetWorkspace, effective), now: context.now() });
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, codeCheckProfileHash(targetWorkspace, effective), replacementOf(run), now, now]);
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.113",
3
+ "version": "0.9.0-beta.115",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {