@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.
- package/dist/git/git-status.d.ts +23 -0
- package/dist/index.js +98 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -29
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-refine-gates.d.ts +29 -0
- package/package.json +3 -3
- package/src/commands/router-refine.ts +27 -2
- package/src/git/git-status.ts +84 -29
- package/src/mesh/mesh-refine-gates.ts +113 -7
package/dist/git/git-status.d.ts
CHANGED
|
@@ -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 ? "
|
|
418
|
-
const commitShort = readInjected(true ? "
|
|
419
|
-
const version = readInjected(true ? "0.9.82-rc.
|
|
420
|
-
const builtAt = readInjected(true ? "2026-07-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
59210
|
-
|
|
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
|
-
|
|
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.
|
|
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");
|