@adhdev/daemon-core 0.9.82-rc.486 → 0.9.82-rc.487

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.
@@ -67,6 +67,29 @@ export interface GitStatusOptions {
67
67
  */
68
68
  export declare const GIT_FETCH_THROTTLE_MS = 30000;
69
69
  export declare function getGitRepoStatus(workspace: string, options?: GitStatusOptions): Promise<GitRepoStatus>;
70
+ /** Coarse change-impact verdict produced from a changed-file list. */
71
+ export interface ChangedPackageClassification {
72
+ isDaemonAffecting: boolean;
73
+ affectedPackages: string[];
74
+ }
75
+ /**
76
+ * Ref-parameterized change-impact classification for a repo/worktree, reusing the
77
+ * exact daemon-vs-web bucketing that the stale-build detector uses — but over an
78
+ * arbitrary `fromRef..toRef` range (e.g. a refine base head → branch head) instead
79
+ * of the live daemon's build commit → HEAD, and WITHOUT any daemonBuildInfo caching.
80
+ *
81
+ * Policy is resolved the same way as getGitRepoStatus: an explicit
82
+ * `options.changeImpactConfig` wins; otherwise the repo's `.adhdev/change-impact.*`
83
+ * is auto-loaded; otherwise the built-in ADHDev default policy applies. The
84
+ * classification uses `git diff --name-only fromRef..toRef`.
85
+ *
86
+ * FAIL-OPEN on error: if the diff can't be collected (bad ref, not a repo), the
87
+ * caller should treat "no verdict" as "run everything" — so we throw rather than
88
+ * returning a misleading benign verdict. Callers wrap this in try/catch and leave
89
+ * changeImpact undefined on failure. Unclassified/new packages still default to
90
+ * isDaemonAffecting:true (never silently skipped).
91
+ */
92
+ export declare function classifyChangedPackages(repoPath: string, fromRef: string, toRef: string, options?: GitStatusOptions): Promise<ChangedPackageClassification>;
70
93
  interface ParsedPorcelainStatus {
71
94
  branch: string | null;
72
95
  /** Full HEAD object id from `# branch.oid`, or null when detached/unborn. */
package/dist/index.js CHANGED
@@ -414,10 +414,10 @@ function readInjected(value) {
414
414
  }
415
415
  function getDaemonBuildInfo() {
416
416
  if (cached) return cached;
417
- const commit = readInjected(true ? "cae0f42043ded25e4d3bed93d9f658035c09e007" : void 0) ?? "unknown";
418
- const commitShort = readInjected(true ? "cae0f420" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
419
- const version = readInjected(true ? "0.9.82-rc.486" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
420
- const builtAt = readInjected(true ? "2026-07-09T23:19:53.642Z" : void 0);
417
+ const commit = readInjected(true ? "ef3ded0f5df148982ed222411ea08c6ab0fdb39b" : void 0) ?? "unknown";
418
+ const commitShort = readInjected(true ? "ef3ded0f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
419
+ const version = readInjected(true ? "0.9.82-rc.487" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
420
+ const builtAt = readInjected(true ? "2026-07-10T01:53:17.254Z" : void 0);
421
421
  cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
422
422
  return cached;
423
423
  }
@@ -789,30 +789,41 @@ function isNonRuntimeRootFile(file, policy) {
789
789
  }
790
790
  return false;
791
791
  }
792
+ function classifyChangedFileList(files, policy) {
793
+ if (files.length === 0) {
794
+ return { isDaemonAffecting: true, affectedPackages: [] };
795
+ }
796
+ const pkgs = /* @__PURE__ */ new Set();
797
+ let sawRuntimeAmbiguousNonPackage = false;
798
+ for (const file of files) {
799
+ const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
800
+ if (!match) {
801
+ if (!isNonRuntimeRootFile(file, policy)) sawRuntimeAmbiguousNonPackage = true;
802
+ continue;
803
+ }
804
+ pkgs.add(match[1]);
805
+ }
806
+ const affectedPackages = [...pkgs].sort();
807
+ const allBenign = !sawRuntimeAmbiguousNonPackage && affectedPackages.every((p) => policy.webOnlyPackages.has(p) && !policy.daemonRuntimePackages.has(p));
808
+ return { isDaemonAffecting: !allBenign, affectedPackages };
809
+ }
792
810
  async function classifyDaemonBuildChange(repoPath, buildCommit, options, policy) {
793
811
  try {
794
812
  const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
795
813
  const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
796
- if (files.length === 0) {
797
- return { isDaemonAffecting: true, affectedPackages: [] };
798
- }
799
- const pkgs = /* @__PURE__ */ new Set();
800
- let sawRuntimeAmbiguousNonPackage = false;
801
- for (const file of files) {
802
- const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
803
- if (!match) {
804
- if (!isNonRuntimeRootFile(file, policy)) sawRuntimeAmbiguousNonPackage = true;
805
- continue;
806
- }
807
- pkgs.add(match[1]);
808
- }
809
- const affectedPackages = [...pkgs].sort();
810
- const allBenign = !sawRuntimeAmbiguousNonPackage && affectedPackages.every((p) => policy.webOnlyPackages.has(p) && !policy.daemonRuntimePackages.has(p));
811
- return { isDaemonAffecting: !allBenign, affectedPackages };
814
+ return classifyChangedFileList(files, policy);
812
815
  } catch {
813
816
  return { isDaemonAffecting: true, affectedPackages: [] };
814
817
  }
815
818
  }
819
+ async function classifyChangedPackages(repoPath, fromRef, toRef, options = {}) {
820
+ const repo = await resolveGitRepository(repoPath, options);
821
+ const { config } = resolveChangeImpactConfigForRepo(repo.repoRoot, options);
822
+ const policy = resolveChangeImpactPolicy(config);
823
+ const diff = await runGit(repoPath, ["diff", "--name-only", `${fromRef}..${toRef}`], options);
824
+ const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
825
+ return classifyChangedFileList(files, policy);
826
+ }
816
827
  function resolveChangeImpactConfigForRepo(repoRoot, options) {
817
828
  if (options.changeImpactConfig === null) {
818
829
  return { config: null, sourceKey: "forced-default" };
@@ -58219,6 +58230,7 @@ function orderMeshRefineBatchNodes(changeAreas) {
58219
58230
 
58220
58231
  // src/commands/router-refine.ts
58221
58232
  init_repo_mesh_types();
58233
+ init_git_status();
58222
58234
  init_mesh_node_identity();
58223
58235
 
58224
58236
  // src/mesh/mesh-refine-gates.ts
@@ -59159,6 +59171,41 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
59159
59171
  if (fs31.existsSync((0, import_path14.join)(cwd, "node_modules"))) return false;
59160
59172
  return ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb", "bun.lock"].some((lock) => fs31.existsSync((0, import_path14.join)(cwd, lock)));
59161
59173
  };
59174
+ const needsNodeModules = (candidate, cwd) => isPackageManagerValidation(candidate) && dependenciesLikelyMissing(cwd);
59175
+ const isDaemonScopedCommand = (candidate) => {
59176
+ const haystack = [candidate.command, ...candidate.args || [], candidate.displayCommand || ""].join(" ").toLowerCase();
59177
+ if (candidate.category === "typecheck") return false;
59178
+ if (/\btypecheck\b/.test(haystack)) return false;
59179
+ if (/\bweb-core\b|\bweb-cloud\b|\bweb-standalone\b|\btest:web\b/.test(haystack)) return false;
59180
+ return /\bdaemon-core\b|\bdaemon-cloud\b|\btest:daemon\b|check-vendor-drift/.test(haystack);
59181
+ };
59182
+ const scopeUnaffectedDaemon = opts?.changeImpact?.isDaemonAffecting === false;
59183
+ const skippedDaemonCommands = [];
59184
+ const commandsToRun = [];
59185
+ for (const candidate of selection.commands) {
59186
+ if (scopeUnaffectedDaemon && isDaemonScopedCommand(candidate)) {
59187
+ skippedDaemonCommands.push(candidate.displayCommand);
59188
+ summary.commandsRun.push({
59189
+ command: candidate.command,
59190
+ args: candidate.args,
59191
+ displayCommand: candidate.displayCommand,
59192
+ category: candidate.category,
59193
+ source: candidate.source,
59194
+ passed: true,
59195
+ skipped: true,
59196
+ skipReason: "unaffected_daemon_scope"
59197
+ });
59198
+ continue;
59199
+ }
59200
+ commandsToRun.push(candidate);
59201
+ }
59202
+ if (opts?.changeImpact) {
59203
+ summary.changeImpact = {
59204
+ isDaemonAffecting: opts.changeImpact.isDaemonAffecting,
59205
+ affectedPackages: opts.changeImpact.affectedPackages,
59206
+ ...skippedDaemonCommands.length ? { skippedDaemonCommands } : {}
59207
+ };
59208
+ }
59162
59209
  if (runLegacyBootstrapCommands) {
59163
59210
  summary.bootstrap = { stage: "legacy" };
59164
59211
  for (const candidate of selection.bootstrapCommands) {
@@ -59193,23 +59240,22 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
59193
59240
  }
59194
59241
  }
59195
59242
  }
59196
- for (const candidate of selection.commands) {
59243
+ let missingDepsBlocked = false;
59244
+ for (const candidate of commandsToRun) {
59197
59245
  const startedAt = Date.now();
59198
59246
  const cwd = candidate.cwd ? (0, import_path14.resolve)(workspace, candidate.cwd) : workspace;
59199
59247
  const timeout = candidate.timeoutMs || REFINE_VALIDATION_TIMEOUT_MS;
59200
59248
  const bootstrapProvidedDependencies = summary.bootstrap?.stage === "cached" || summary.bootstrap?.stage === "ran" || summary.bootstrap?.stage === "legacy";
59201
- if (!bootstrapProvidedDependencies && isPackageManagerValidation(candidate) && dependenciesLikelyMissing(cwd)) {
59249
+ if (!bootstrapProvidedDependencies && needsNodeModules(candidate, cwd)) {
59202
59250
  summary.commandsRun.push(commandRecord(candidate, cwd, startedAt, {
59203
- stderr: "Dependencies appear to be missing: package.json and a lockfile are present, but node_modules is absent. Configure validation.bootstrapCommands in repo mesh/refine config if Refinery should install/bootstrap before validation."
59251
+ stderr: "Dependencies appear to be missing: package.json and a lockfile are present, but node_modules is absent. Configure validation.bootstrapCommands (or .adhdev/worktree_bootstrap.json) in repo mesh/refine config if Refinery should install/bootstrap before validation."
59204
59252
  }, false, {
59205
59253
  exitCode: null,
59206
59254
  skipped: true,
59207
59255
  failureKind: "missing_dependencies"
59208
59256
  }));
59209
- summary.status = "failed";
59210
- summary.failureKind = "missing_dependencies";
59211
- summary.failureCode = "missing_dependencies";
59212
- return summary;
59257
+ missingDepsBlocked = true;
59258
+ continue;
59213
59259
  }
59214
59260
  const resolvedCommand = resolveWin32Executable(candidate.command);
59215
59261
  const spawn5 = buildWin32ExecFileSpawn(resolvedCommand, candidate.args);
@@ -59245,6 +59291,12 @@ async function runMeshRefineValidationGate(mesh, workspace, opts) {
59245
59291
  return summary;
59246
59292
  }
59247
59293
  }
59294
+ if (missingDepsBlocked) {
59295
+ summary.status = "failed";
59296
+ summary.failureKind = "missing_dependencies";
59297
+ summary.failureCode = "missing_dependencies";
59298
+ return summary;
59299
+ }
59248
59300
  summary.status = "passed";
59249
59301
  return summary;
59250
59302
  }
@@ -59452,7 +59504,20 @@ async function refineResolveRefsStage(self, meshId, nodeId, args, refineStages)
59452
59504
  const { stdout: branchHeadStdout } = await execFileAsync4("git", ["rev-parse", branch], { cwd: node.workspace, encoding: "utf8" });
59453
59505
  const baseHead = baseHeadRaw;
59454
59506
  const branchHead = branchHeadStdout.trim();
59455
- recordMeshRefineStage(refineStages, "resolve_refs", "passed", resolveStarted, { branch, baseBranch, baseHead, branchHead, ...fetchWarning ? { fetchWarning } : {} });
59507
+ let changeImpact;
59508
+ try {
59509
+ changeImpact = await classifyChangedPackages(node.workspace, baseHead, branchHead);
59510
+ } catch {
59511
+ changeImpact = void 0;
59512
+ }
59513
+ recordMeshRefineStage(refineStages, "resolve_refs", "passed", resolveStarted, {
59514
+ branch,
59515
+ baseBranch,
59516
+ baseHead,
59517
+ branchHead,
59518
+ ...changeImpact ? { changeImpact } : {},
59519
+ ...fetchWarning ? { fetchWarning } : {}
59520
+ });
59456
59521
  return {
59457
59522
  kind: "continue",
59458
59523
  ctx: {
@@ -59469,6 +59534,7 @@ async function refineResolveRefsStage(self, meshId, nodeId, args, refineStages)
59469
59534
  baseBranch,
59470
59535
  baseHead,
59471
59536
  branchHead,
59537
+ changeImpact,
59472
59538
  validationSummary: void 0,
59473
59539
  patchEquivalence: void 0,
59474
59540
  submoduleReachability: void 0
@@ -59479,6 +59545,9 @@ async function refineValidationStage(self, ctx) {
59479
59545
  const { mesh, node, branch, baseBranch, refineStages } = ctx;
59480
59546
  const validationStarted = Date.now();
59481
59547
  const validationSummary = await runMeshRefineValidationGate(mesh, node.workspace, {
59548
+ // (a) Scope the validation command set by coarse change-impact (resolved
59549
+ // in resolve_refs). Undefined → gate runs the full command set (fail-open).
59550
+ changeImpact: ctx.changeImpact,
59482
59551
  // M2-2: consume the node's persisted bootstrap state; persist re-runs.
59483
59552
  persistedBootstrapState: node.worktreeBootstrap,
59484
59553
  onBootstrapStateChange: (state) => {
@@ -59498,7 +59567,7 @@ async function refineValidationStage(self, ctx) {
59498
59567
  if (validationSummary.status === "failed") {
59499
59568
  const firstFailedCmd = Array.isArray(validationSummary.commandsRun) ? validationSummary.commandsRun.find((c) => c.success === false) : void 0;
59500
59569
  const buildValidationFailedError = () => {
59501
- const base = validationSummary.failureCode === "missing_dependencies" ? "Refinery validation dependencies are missing; merge/refine was not attempted. Configure validation.bootstrapCommands if Refinery should bootstrap dependencies before validation." : validationSummary.failureCode === "dependency_bootstrap_failed" ? "Refinery dependency/bootstrap command failed; merge/refine was not attempted." : validationSummary.failureCode === "spawn_resolution_failed" ? validationSummary.spawnResolutionError || "Refinery validation command could not be spawned (executable not found); merge/refine was not attempted." : "Refinery validation gate failed; merge/refine was not attempted.";
59570
+ const base = validationSummary.failureCode === "missing_dependencies" ? "Refinery validation dependencies are missing for a change-affected package; merge/refine was not attempted. To make this self-service, either (1) configure .adhdev/worktree_bootstrap.json (or validation.bootstrapCommands in .adhdev/refine.json) so Refinery installs deps before validation, or (2) converge the branch via the documented manual fast-forward-only bypass (rebase onto the fetched base, verify strict ancestry, then push ff-only) instead of the refine gate." : validationSummary.failureCode === "dependency_bootstrap_failed" ? "Refinery dependency/bootstrap command failed; merge/refine was not attempted." : validationSummary.failureCode === "spawn_resolution_failed" ? validationSummary.spawnResolutionError || "Refinery validation command could not be spawned (executable not found); merge/refine was not attempted." : "Refinery validation gate failed; merge/refine was not attempted.";
59502
59571
  if (!firstFailedCmd) return base;
59503
59572
  const cmdName = typeof firstFailedCmd.displayCommand === "string" ? firstFailedCmd.displayCommand : typeof firstFailedCmd.command === "string" ? [firstFailedCmd.command, ...Array.isArray(firstFailedCmd.args) ? firstFailedCmd.args : []].join(" ").trim() : typeof firstFailedCmd.cmd === "string" ? firstFailedCmd.cmd : "";
59504
59573
  const rawOutput = [firstFailedCmd.stdout, firstFailedCmd.stderr, firstFailedCmd.output].filter((s2) => typeof s2 === "string" && s2.length > 0).join("\n");