@smartergpt/lexrunner 2.1.0 → 2.2.0
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 +28 -0
- package/README.mcp.md +5 -1
- package/README.md +40 -33
- package/dist/{autopilot-JWIOXBCZ.js → autopilot-7PX6YG55.js} +1 -1
- package/dist/{chunk-SHSL643G.js → chunk-M6AAYMHH.js} +361 -291
- package/dist/cli.cjs +2763 -2516
- package/dist/cli.d.ts +40 -17
- package/dist/cli.js +275 -109
- package/dist/sdk/index.d.ts +5 -5
- package/package.json +3 -3
- package/schemas/plan.schema.json +203 -73
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
AutopilotConfigError,
|
|
34
34
|
FileAnalyzer,
|
|
35
|
+
FrozenGitInputs,
|
|
35
36
|
GATE_EXECUTION_RECEIPT_SCHEMA_VERSION,
|
|
36
37
|
GitHubAuthError,
|
|
37
38
|
GitHubClientImpl,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
75
76
|
readRunLog,
|
|
76
77
|
readRunState,
|
|
77
78
|
removeIndexEntry,
|
|
79
|
+
repositoryIdentity,
|
|
78
80
|
resolveProfile,
|
|
79
81
|
resolveSpawnExecutable,
|
|
80
82
|
runEnvironmentQualityCheck,
|
|
@@ -83,7 +85,7 @@ import {
|
|
|
83
85
|
validateWriteOperation,
|
|
84
86
|
verifyToolchainPins,
|
|
85
87
|
writeRunState
|
|
86
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-M6AAYMHH.js";
|
|
87
89
|
import {
|
|
88
90
|
ExecutionState,
|
|
89
91
|
generateMarkdownSummary,
|
|
@@ -474,7 +476,7 @@ import "dotenv/config";
|
|
|
474
476
|
// package.json
|
|
475
477
|
var package_default = {
|
|
476
478
|
name: "@smartergpt/lexrunner",
|
|
477
|
-
version: "2.
|
|
479
|
+
version: "2.2.0",
|
|
478
480
|
type: "module",
|
|
479
481
|
engines: {
|
|
480
482
|
node: ">=24"
|
|
@@ -484,7 +486,7 @@ var package_default = {
|
|
|
484
486
|
description: "Fan-out tasks as multiple PRs in parallel, then build a merge pyramid from the blocks. Compute dependency order, run gates locally, and merge cleanly.",
|
|
485
487
|
repository: {
|
|
486
488
|
type: "git",
|
|
487
|
-
url: "git+https://github.com/
|
|
489
|
+
url: "git+https://github.com/SmarterGPT/lexrunner.git"
|
|
488
490
|
},
|
|
489
491
|
publishConfig: {
|
|
490
492
|
access: "public",
|
|
@@ -577,7 +579,7 @@ var package_default = {
|
|
|
577
579
|
"docs:surface:check": "node scripts/render-doc-surfaces.mjs --check",
|
|
578
580
|
"dogfood:ecosystem": "tsx scripts/dogfood-ecosystem.ts",
|
|
579
581
|
test: "vitest run",
|
|
580
|
-
"test:git": "vitest run -c vitest.git.config.ts tests/release-prepare.spec.ts tests/promptsResolver.spec.ts tests/e2e-comprehensive.test.ts tests/autopilot-e2e-level3-4.spec.ts tests/deterministic-build.test.ts tests/e2e-merge-resume.spec.ts",
|
|
582
|
+
"test:git": "vitest run -c vitest.git.config.ts tests/release-prepare.spec.ts tests/promptsResolver.spec.ts tests/e2e-comprehensive.test.ts tests/autopilot-e2e-level3-4.spec.ts tests/deterministic-build.test.ts tests/e2e-merge-resume.spec.ts tests/e2e-frozen-git-inputs.spec.ts",
|
|
581
583
|
"test:isolation": "node scripts/verify-parallel-isolation.mjs",
|
|
582
584
|
"test:cli:slow": "vitest run -c vitest.slow-cli.config.ts",
|
|
583
585
|
benchmark: "vitest bench -c vitest.benchmark.config.ts",
|
|
@@ -2644,13 +2646,41 @@ function installUnhandledRejectionHandler() {
|
|
|
2644
2646
|
unhandledRejectionHandlerInstalled = true;
|
|
2645
2647
|
}
|
|
2646
2648
|
|
|
2649
|
+
// src/core/standardGateCommands.ts
|
|
2650
|
+
function getStandardGateCommand(gateName) {
|
|
2651
|
+
const commands = {
|
|
2652
|
+
lint: "npm run lint",
|
|
2653
|
+
test: "npm test",
|
|
2654
|
+
unit: "npm test",
|
|
2655
|
+
typecheck: "npm run typecheck",
|
|
2656
|
+
build: "npm run build",
|
|
2657
|
+
format: "npm run format"
|
|
2658
|
+
};
|
|
2659
|
+
if (!Object.prototype.hasOwnProperty.call(commands, gateName)) {
|
|
2660
|
+
throw new Error(
|
|
2661
|
+
`No executable command is defined for gate ${JSON.stringify(gateName)}. Author an explicit gate run command in a manual plan, or select a supported standard gate.`
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
return commands[gateName];
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2647
2667
|
// src/core/githubPlan.ts
|
|
2648
2668
|
var STANDARD_TEST_GATE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
2649
2669
|
async function generatePlanFromGitHub(client, options = {}) {
|
|
2650
2670
|
const repoInfo = await client.validateRepository();
|
|
2651
2671
|
const target = options.target || repoInfo.defaultBranch;
|
|
2672
|
+
const targetCommit = await client.getBranchHead(target);
|
|
2673
|
+
const gitInputs = {
|
|
2674
|
+
schemaVersion: "1.0.0",
|
|
2675
|
+
repository: `${repoInfo.url.replace(/\/$/, "").replace(/\.git$/, "")}.git`,
|
|
2676
|
+
checkoutRemote: "origin",
|
|
2677
|
+
acquisition: options.gitAcquisition ?? "fetch",
|
|
2678
|
+
target: { ref: `refs/heads/${target}`, commit: targetCommit },
|
|
2679
|
+
sources: []
|
|
2680
|
+
};
|
|
2652
2681
|
const prs = await client.listOpenPRs({
|
|
2653
2682
|
state: "open",
|
|
2683
|
+
base: target,
|
|
2654
2684
|
labels: options.labels,
|
|
2655
2685
|
// If no specific query, find PRs with stack labels or open PRs
|
|
2656
2686
|
...options.query ? { query: options.query } : {}
|
|
@@ -2661,8 +2691,9 @@ async function generatePlanFromGitHub(client, options = {}) {
|
|
|
2661
2691
|
const finalPRs = excludePRs.length > 0 ? filteredPRs.filter((pr) => !excludePRs.includes(pr.number)) : filteredPRs;
|
|
2662
2692
|
if (finalPRs.length === 0) {
|
|
2663
2693
|
const emptyPlan = {
|
|
2664
|
-
schemaVersion: "1.0.
|
|
2694
|
+
schemaVersion: "1.0.1",
|
|
2665
2695
|
target,
|
|
2696
|
+
gitInputs,
|
|
2666
2697
|
items: []
|
|
2667
2698
|
};
|
|
2668
2699
|
if (options.policy) {
|
|
@@ -2676,15 +2707,28 @@ async function generatePlanFromGitHub(client, options = {}) {
|
|
|
2676
2707
|
mergeRule: { type: "strict-required" }
|
|
2677
2708
|
};
|
|
2678
2709
|
}
|
|
2679
|
-
return emptyPlan;
|
|
2710
|
+
return Plan.parse(emptyPlan);
|
|
2680
2711
|
}
|
|
2681
2712
|
const prDetails = await Promise.all(finalPRs.map((pr) => client.getPRDetails(pr.number)));
|
|
2682
|
-
|
|
2713
|
+
if (prDetails.some((pr) => pr.base.ref !== target)) {
|
|
2714
|
+
throw new Error(
|
|
2715
|
+
"Selected PR targets a different branch; refresh discovery for the intended target"
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2718
|
+
const planItems = prDetails.map(
|
|
2719
|
+
(pr) => transformPRToPlanItem(pr, options, `${repoInfo.owner}/${repoInfo.repo}`)
|
|
2720
|
+
);
|
|
2721
|
+
gitInputs.sources = prDetails.map((pr) => ({
|
|
2722
|
+
item: `PR-${pr.number}`,
|
|
2723
|
+
ref: `refs/pull/${pr.number}/head`,
|
|
2724
|
+
commit: pr.head.sha
|
|
2725
|
+
})).sort((a, b) => a.item.localeCompare(b.item));
|
|
2683
2726
|
planItems.sort((a, b) => a.name.localeCompare(b.name));
|
|
2684
2727
|
validateGitHubDependencies(planItems, prDetails);
|
|
2685
2728
|
const plan = {
|
|
2686
|
-
schemaVersion: "1.0.
|
|
2729
|
+
schemaVersion: "1.0.1",
|
|
2687
2730
|
target,
|
|
2731
|
+
gitInputs,
|
|
2688
2732
|
items: planItems
|
|
2689
2733
|
};
|
|
2690
2734
|
if (options.policy) {
|
|
@@ -2698,12 +2742,18 @@ async function generatePlanFromGitHub(client, options = {}) {
|
|
|
2698
2742
|
mergeRule: { type: "strict-required" }
|
|
2699
2743
|
};
|
|
2700
2744
|
}
|
|
2701
|
-
return plan;
|
|
2745
|
+
return Plan.parse(plan);
|
|
2702
2746
|
}
|
|
2703
|
-
function transformPRToPlanItem(pr, options) {
|
|
2747
|
+
function transformPRToPlanItem(pr, options, repository) {
|
|
2704
2748
|
const name = `PR-${pr.number}`;
|
|
2705
2749
|
const deps = pr.dependencies.filter((dep) => {
|
|
2706
|
-
|
|
2750
|
+
const match = /^(?:([^#]+))?#([1-9][0-9]*)$/.exec(dep);
|
|
2751
|
+
if (!match || match[1] && match[1].toLowerCase() !== repository.toLowerCase()) {
|
|
2752
|
+
throw new Error(
|
|
2753
|
+
`Dependency ${JSON.stringify(dep)} is outside this single-repository plan; declare a valid repository-bound dependency`
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
return true;
|
|
2707
2757
|
}).map((dep) => {
|
|
2708
2758
|
const prNumber = dep.includes("#") ? dep.split("#")[1] : dep;
|
|
2709
2759
|
return `PR-${prNumber}`;
|
|
@@ -2738,47 +2788,7 @@ function createStandardGate(gateName, pr) {
|
|
|
2738
2788
|
runtime: "local",
|
|
2739
2789
|
artifacts: []
|
|
2740
2790
|
};
|
|
2741
|
-
|
|
2742
|
-
case "lint":
|
|
2743
|
-
return {
|
|
2744
|
-
...baseGate,
|
|
2745
|
-
run: "npm run lint",
|
|
2746
|
-
artifacts: ["lint-results.txt"]
|
|
2747
|
-
};
|
|
2748
|
-
case "test":
|
|
2749
|
-
case "unit":
|
|
2750
|
-
return {
|
|
2751
|
-
...baseGate,
|
|
2752
|
-
run: "npm test",
|
|
2753
|
-
artifacts: ["test-results.xml", "coverage/"],
|
|
2754
|
-
timeoutMs: STANDARD_TEST_GATE_TIMEOUT_MS
|
|
2755
|
-
};
|
|
2756
|
-
case "typecheck":
|
|
2757
|
-
return {
|
|
2758
|
-
...baseGate,
|
|
2759
|
-
run: "npm run typecheck",
|
|
2760
|
-
artifacts: ["typecheck-results.txt"]
|
|
2761
|
-
};
|
|
2762
|
-
case "build":
|
|
2763
|
-
return {
|
|
2764
|
-
...baseGate,
|
|
2765
|
-
run: "npm run build",
|
|
2766
|
-
artifacts: ["dist/", "build-log.txt"]
|
|
2767
|
-
};
|
|
2768
|
-
default:
|
|
2769
|
-
return baseGate;
|
|
2770
|
-
}
|
|
2771
|
-
}
|
|
2772
|
-
function getStandardGateCommand(gateName) {
|
|
2773
|
-
const standardCommands = {
|
|
2774
|
-
lint: "npm run lint",
|
|
2775
|
-
test: "npm test",
|
|
2776
|
-
unit: "npm test",
|
|
2777
|
-
typecheck: "npm run typecheck",
|
|
2778
|
-
build: "npm run build",
|
|
2779
|
-
format: "npm run format"
|
|
2780
|
-
};
|
|
2781
|
-
return standardCommands[gateName] || `echo "Running ${gateName} gate"`;
|
|
2791
|
+
return gateName === "test" || gateName === "unit" ? { ...baseGate, timeoutMs: STANDARD_TEST_GATE_TIMEOUT_MS } : baseGate;
|
|
2782
2792
|
}
|
|
2783
2793
|
function validateGitHubDependencies(planItems, prDetails) {
|
|
2784
2794
|
const itemNames = new Set(planItems.map((item) => item.name));
|
|
@@ -2928,7 +2938,7 @@ function generateGatesForMultiRepoPR(pr, options) {
|
|
|
2928
2938
|
for (const gateName of requiredGates) {
|
|
2929
2939
|
gates.push({
|
|
2930
2940
|
name: gateName,
|
|
2931
|
-
run:
|
|
2941
|
+
run: getStandardGateCommand(gateName),
|
|
2932
2942
|
env: {
|
|
2933
2943
|
PR_NUMBER: pr.number.toString(),
|
|
2934
2944
|
PR_BRANCH: pr.head.ref,
|
|
@@ -2937,33 +2947,13 @@ function generateGatesForMultiRepoPR(pr, options) {
|
|
|
2937
2947
|
REPO_NAME: pr.repoName
|
|
2938
2948
|
},
|
|
2939
2949
|
runtime: "local",
|
|
2940
|
-
artifacts:
|
|
2950
|
+
artifacts: [],
|
|
2941
2951
|
...isStandardTestGate(gateName) ? { timeoutMs: STANDARD_TEST_GATE_TIMEOUT_MS2 } : {}
|
|
2942
2952
|
});
|
|
2943
2953
|
}
|
|
2944
2954
|
gates.sort((a, b) => a.name.localeCompare(b.name));
|
|
2945
2955
|
return gates;
|
|
2946
2956
|
}
|
|
2947
|
-
function getGateCommand(gateName) {
|
|
2948
|
-
const commands = {
|
|
2949
|
-
lint: "npm run lint",
|
|
2950
|
-
test: "npm test",
|
|
2951
|
-
unit: "npm test",
|
|
2952
|
-
typecheck: "npm run typecheck",
|
|
2953
|
-
build: "npm run build"
|
|
2954
|
-
};
|
|
2955
|
-
return commands[gateName] || `echo "Running ${gateName}"`;
|
|
2956
|
-
}
|
|
2957
|
-
function getGateArtifacts(gateName) {
|
|
2958
|
-
const artifacts = {
|
|
2959
|
-
lint: ["lint-results.txt"],
|
|
2960
|
-
test: ["test-results.xml", "coverage/"],
|
|
2961
|
-
unit: ["test-results.xml", "coverage/"],
|
|
2962
|
-
typecheck: ["typecheck-results.txt"],
|
|
2963
|
-
build: ["dist/"]
|
|
2964
|
-
};
|
|
2965
|
-
return artifacts[gateName] || [];
|
|
2966
|
-
}
|
|
2967
2957
|
function isStandardTestGate(gateName) {
|
|
2968
2958
|
return gateName === "test" || gateName === "unit";
|
|
2969
2959
|
}
|
|
@@ -3491,6 +3481,7 @@ var IntegrationStatusQueryService = class {
|
|
|
3491
3481
|
schemaVersion: validated.schemaVersion,
|
|
3492
3482
|
target: bounded(validated.target),
|
|
3493
3483
|
itemCount: validated.items.length,
|
|
3484
|
+
gitInputBinding: validated.gitInputs ? "frozen" : "legacy-unbound",
|
|
3494
3485
|
...validated.policy ? { policy: validated.policy } : {}
|
|
3495
3486
|
},
|
|
3496
3487
|
mergeSummary: evaluator.getMergeSummary(),
|
|
@@ -10019,6 +10010,70 @@ import { join as join15 } from "path";
|
|
|
10019
10010
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
10020
10011
|
import { ulid } from "ulid";
|
|
10021
10012
|
|
|
10013
|
+
// src/git/frozen-inputs.ts
|
|
10014
|
+
var FrozenGitInputError = class extends Error {
|
|
10015
|
+
constructor(message) {
|
|
10016
|
+
super(message);
|
|
10017
|
+
this.name = "FrozenGitInputError";
|
|
10018
|
+
}
|
|
10019
|
+
};
|
|
10020
|
+
async function verifyFrozenGitInputs(git2, value) {
|
|
10021
|
+
const inputs = FrozenGitInputs.parse(value);
|
|
10022
|
+
let remote;
|
|
10023
|
+
try {
|
|
10024
|
+
remote = (await git2.raw(["remote", "get-url", inputs.checkoutRemote])).trim();
|
|
10025
|
+
if (repositoryIdentity(remote) !== repositoryIdentity(inputs.repository)) throw new Error();
|
|
10026
|
+
} catch {
|
|
10027
|
+
throw new FrozenGitInputError(
|
|
10028
|
+
"Checkout repository does not match gitInputs; use the declared checkout"
|
|
10029
|
+
);
|
|
10030
|
+
}
|
|
10031
|
+
const resolve16 = async (input) => {
|
|
10032
|
+
let ref = input.ref;
|
|
10033
|
+
if (inputs.acquisition === "fetch") {
|
|
10034
|
+
ref = `refs/lexrunner/inputs/${sha256(Buffer.from(`${inputs.repository}
|
|
10035
|
+
${input.ref}
|
|
10036
|
+
${input.commit}`))}`;
|
|
10037
|
+
try {
|
|
10038
|
+
await git2.raw([
|
|
10039
|
+
"fetch",
|
|
10040
|
+
"--no-tags",
|
|
10041
|
+
"--no-recurse-submodules",
|
|
10042
|
+
"--no-write-fetch-head",
|
|
10043
|
+
"--",
|
|
10044
|
+
inputs.repository,
|
|
10045
|
+
`+${input.ref}:${ref}`
|
|
10046
|
+
]);
|
|
10047
|
+
} catch {
|
|
10048
|
+
throw new FrozenGitInputError(
|
|
10049
|
+
`Declared acquisition failed for ${input.ref}; restore access and retry without changing frozen expectations`
|
|
10050
|
+
);
|
|
10051
|
+
}
|
|
10052
|
+
}
|
|
10053
|
+
let observed;
|
|
10054
|
+
try {
|
|
10055
|
+
observed = (await git2.raw(["rev-parse", "--verify", ref])).trim();
|
|
10056
|
+
} catch {
|
|
10057
|
+
throw new FrozenGitInputError(
|
|
10058
|
+
`Declared ref ${input.ref} is unavailable; explicitly acquire it or create a new plan permitting fetch`
|
|
10059
|
+
);
|
|
10060
|
+
}
|
|
10061
|
+
if (observed !== input.commit) {
|
|
10062
|
+
throw new FrozenGitInputError(
|
|
10063
|
+
`Stale Git input ${input.ref}; reassess and generate a new plan instead of replacing its expected commit`
|
|
10064
|
+
);
|
|
10065
|
+
}
|
|
10066
|
+
if ((await git2.raw(["cat-file", "-t", input.commit])).trim() !== "commit") {
|
|
10067
|
+
throw new FrozenGitInputError(`Declared input ${input.ref} is not a commit object`);
|
|
10068
|
+
}
|
|
10069
|
+
return observed;
|
|
10070
|
+
};
|
|
10071
|
+
const targetHeadSha = await resolve16(inputs.target);
|
|
10072
|
+
const sourceHeads = /* @__PURE__ */ Object.create(null);
|
|
10073
|
+
for (const source of inputs.sources) sourceHeads[source.item] = await resolve16(source);
|
|
10074
|
+
return { targetHeadSha, sourceHeads };
|
|
10075
|
+
}
|
|
10076
|
+
|
|
10022
10077
|
// src/weave/resume-service.ts
|
|
10023
10078
|
import { z as z7 } from "zod";
|
|
10024
10079
|
async function resumePersistedWeave(input) {
|
|
@@ -10167,6 +10222,15 @@ function validateResumeJournal(checkpoint) {
|
|
|
10167
10222
|
};
|
|
10168
10223
|
}
|
|
10169
10224
|
const ids = /* @__PURE__ */ new Set();
|
|
10225
|
+
const inputs = parsedPlan.data.gitInputs;
|
|
10226
|
+
if (inputs && journal.repository.integrationBranch !== `weave/resume-${checkpoint.runId.toLowerCase()}`) {
|
|
10227
|
+
return { valid: false, reason: "checkpoint integration branch does not match its bound run" };
|
|
10228
|
+
}
|
|
10229
|
+
if (inputs && (inputs.target.commit !== journal.repository.targetHeadSha || inputs.sources.some(
|
|
10230
|
+
(source) => source.commit !== journal.repository.sourceHeads[source.item]
|
|
10231
|
+
))) {
|
|
10232
|
+
return { valid: false, reason: "checkpoint commits do not match the plan's frozen Git inputs" };
|
|
10233
|
+
}
|
|
10170
10234
|
let seenIncomplete = false;
|
|
10171
10235
|
for (const operation of journal.operations) {
|
|
10172
10236
|
if (!operation.id || ids.has(operation.id)) {
|
|
@@ -10332,11 +10396,22 @@ async function createLocalResumeCheckpoint(input) {
|
|
|
10332
10396
|
const runId = input.runId ?? ulid();
|
|
10333
10397
|
const at = input.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
10334
10398
|
const levels = computeMergeOrder(input.plan);
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10399
|
+
Plan.parse(input.plan);
|
|
10400
|
+
const sourceHeads = /* @__PURE__ */ Object.create(null);
|
|
10401
|
+
let targetHeadSha;
|
|
10402
|
+
if (input.plan.gitInputs) {
|
|
10403
|
+
const status = await git2.status();
|
|
10404
|
+
if (status.files.some(isUnexpectedBoundChange)) {
|
|
10405
|
+
throw new Error("Working tree must be clean before acquiring frozen Git inputs");
|
|
10406
|
+
}
|
|
10407
|
+
const verified = await verifyFrozenGitInputs(git2, input.plan.gitInputs);
|
|
10408
|
+
Object.assign(sourceHeads, verified.sourceHeads);
|
|
10409
|
+
targetHeadSha = verified.targetHeadSha;
|
|
10410
|
+
} else {
|
|
10411
|
+
for (const item of input.plan.items)
|
|
10412
|
+
sourceHeads[item.name] = await exactRefHead(git2, item.name);
|
|
10413
|
+
targetHeadSha = await exactRefHead(git2, input.plan.target);
|
|
10338
10414
|
}
|
|
10339
|
-
const targetHeadSha = await exactRefHead(git2, input.plan.target);
|
|
10340
10415
|
const integrationBranch = `weave/resume-${runId.toLowerCase()}`;
|
|
10341
10416
|
const operations = planResumeOperations(input.plan);
|
|
10342
10417
|
const planHash = sha256(Buffer.from(canonicalJSONStringify(input.plan)));
|
|
@@ -10358,7 +10433,7 @@ async function createLocalResumeCheckpoint(input) {
|
|
|
10358
10433
|
lastUpdatedAt: at,
|
|
10359
10434
|
metadata: {
|
|
10360
10435
|
target: input.plan.target,
|
|
10361
|
-
warnings: [],
|
|
10436
|
+
warnings: input.plan.gitInputs ? [] : ["Legacy unbound plan: Git heads are selected at preparation, not frozen at synthesis"],
|
|
10362
10437
|
resume: createResumeJournal({
|
|
10363
10438
|
operations,
|
|
10364
10439
|
target: input.plan.target,
|
|
@@ -10380,23 +10455,34 @@ var LocalWeaveResumeDriver = class {
|
|
|
10380
10455
|
if (!journal) return { valid: false, reason: "resume journal is missing" };
|
|
10381
10456
|
try {
|
|
10382
10457
|
const status = await this.git.status();
|
|
10383
|
-
const unexpectedChanges = status.files.filter(
|
|
10458
|
+
const unexpectedChanges = status.files.filter(
|
|
10459
|
+
checkpoint.plan.gitInputs ? isUnexpectedBoundChange : (file) => !isRunnerStatePath(file.path)
|
|
10460
|
+
);
|
|
10384
10461
|
if (unexpectedChanges.length > 0) {
|
|
10385
10462
|
return {
|
|
10386
10463
|
valid: false,
|
|
10387
10464
|
reason: "working tree is not clean; resolve or abort the interrupted operation first"
|
|
10388
10465
|
};
|
|
10389
10466
|
}
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10467
|
+
if (checkpoint.plan.gitInputs) {
|
|
10468
|
+
const verified = await verifyFrozenGitInputs(this.git, checkpoint.plan.gitInputs);
|
|
10469
|
+
if (verified.targetHeadSha !== journal.repository.targetHeadSha || Object.entries(verified.sourceHeads).some(
|
|
10470
|
+
([name, commit]) => journal.repository.sourceHeads[name] !== commit
|
|
10471
|
+
)) {
|
|
10472
|
+
return { valid: false, reason: "Journal commit evidence differs from frozen Git inputs" };
|
|
10473
|
+
}
|
|
10474
|
+
} else {
|
|
10475
|
+
const targetHead = await exactRefHead(this.git, journal.repository.target);
|
|
10476
|
+
if (targetHead !== journal.repository.targetHeadSha) {
|
|
10477
|
+
return { valid: false, reason: "target branch changed after the checkpoint was created" };
|
|
10478
|
+
}
|
|
10479
|
+
for (const [name, expected] of Object.entries(journal.repository.sourceHeads)) {
|
|
10480
|
+
if (await exactRefHead(this.git, name) !== expected) {
|
|
10481
|
+
return {
|
|
10482
|
+
valid: false,
|
|
10483
|
+
reason: `source branch ${name} changed after checkpoint creation`
|
|
10484
|
+
};
|
|
10485
|
+
}
|
|
10400
10486
|
}
|
|
10401
10487
|
}
|
|
10402
10488
|
const integrationHead = await localBranchHead(this.git, journal.repository.integrationBranch);
|
|
@@ -10447,29 +10533,66 @@ var LocalWeaveResumeDriver = class {
|
|
|
10447
10533
|
}
|
|
10448
10534
|
async execute(operation, checkpoint) {
|
|
10449
10535
|
const journal = checkpoint.metadata.resume;
|
|
10536
|
+
const bound4 = !!checkpoint.plan.gitInputs;
|
|
10537
|
+
const expectedIntegration = lastCompletedMergeSha(journal.operations) ?? journal.repository.targetHeadSha;
|
|
10538
|
+
if (bound4) {
|
|
10539
|
+
const stateError = await this.boundStateError();
|
|
10540
|
+
if (stateError) return { completed: false, reason: stateError };
|
|
10541
|
+
const integrationHead = await localBranchHead(this.git, journal.repository.integrationBranch);
|
|
10542
|
+
if (integrationHead && integrationHead !== expectedIntegration || !integrationHead && lastCompletedMergeSha(journal.operations)) {
|
|
10543
|
+
return {
|
|
10544
|
+
completed: false,
|
|
10545
|
+
reason: "Frozen integration branch changed before operation; preserve and inspect it"
|
|
10546
|
+
};
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10450
10549
|
await this.ensureIntegrationBranch(
|
|
10451
10550
|
journal.repository.integrationBranch,
|
|
10452
10551
|
journal.repository.targetHeadSha
|
|
10453
10552
|
);
|
|
10454
10553
|
if (operation.phase === "merge") {
|
|
10455
10554
|
await this.git.checkout(journal.repository.integrationBranch);
|
|
10555
|
+
if (bound4) {
|
|
10556
|
+
const stateError = await this.boundStateError(expectedIntegration);
|
|
10557
|
+
if (stateError) return { completed: false, reason: stateError };
|
|
10558
|
+
}
|
|
10456
10559
|
const item = checkpoint.plan.items.find((candidate) => candidate.name === operation.item);
|
|
10457
10560
|
if (!item) return { completed: false, reason: "plan item is missing" };
|
|
10458
10561
|
const result2 = await this.operations.executeMergeOperation({
|
|
10459
10562
|
item,
|
|
10460
10563
|
targetBranch: journal.repository.integrationBranch,
|
|
10461
|
-
strategy: "merge-weave"
|
|
10564
|
+
strategy: "merge-weave",
|
|
10565
|
+
...checkpoint.plan.gitInputs ? { sourceCommit: journal.repository.sourceHeads[operation.item] } : {}
|
|
10462
10566
|
});
|
|
10567
|
+
if (bound4 && result2.success && result2.sha) {
|
|
10568
|
+
const stateError = await this.boundStateError(result2.sha);
|
|
10569
|
+
if (stateError) return { completed: false, reason: stateError };
|
|
10570
|
+
}
|
|
10463
10571
|
return result2.success && result2.sha ? { completed: true, externalId: result2.sha } : { completed: false, reason: result2.message ?? "merge failed" };
|
|
10464
10572
|
}
|
|
10465
10573
|
const gate = gateForOperation(checkpoint.plan, operation);
|
|
10466
10574
|
if (!gate) return { completed: false, reason: "gate definition is missing" };
|
|
10467
10575
|
if (operation.phase === "gate") {
|
|
10468
|
-
|
|
10576
|
+
if (checkpoint.plan.gitInputs) {
|
|
10577
|
+
await this.git.checkout(["--detach", journal.repository.sourceHeads[operation.item]]);
|
|
10578
|
+
} else {
|
|
10579
|
+
await this.git.checkout(`origin/${operation.item}`).catch(() => this.git.checkout(operation.item));
|
|
10580
|
+
}
|
|
10469
10581
|
} else {
|
|
10470
10582
|
await this.git.checkout(journal.repository.integrationBranch);
|
|
10471
10583
|
}
|
|
10472
|
-
const
|
|
10584
|
+
const expectedGateHead = operation.phase === "gate" ? journal.repository.sourceHeads[operation.item] : expectedIntegration;
|
|
10585
|
+
if (bound4) {
|
|
10586
|
+
const stateError = await this.boundStateError(expectedGateHead);
|
|
10587
|
+
if (stateError) return { completed: false, reason: stateError };
|
|
10588
|
+
}
|
|
10589
|
+
const artifactDir = join15(
|
|
10590
|
+
this.workingDir,
|
|
10591
|
+
".lexrunner",
|
|
10592
|
+
"runs",
|
|
10593
|
+
checkpoint.runId,
|
|
10594
|
+
operation.phase
|
|
10595
|
+
);
|
|
10473
10596
|
await mkdir2(artifactDir, { recursive: true });
|
|
10474
10597
|
const policy = Policy.parse(checkpoint.plan.policy ?? {});
|
|
10475
10598
|
const result = await executeGate(
|
|
@@ -10481,12 +10604,38 @@ var LocalWeaveResumeDriver = class {
|
|
|
10481
10604
|
false,
|
|
10482
10605
|
this.workingDir
|
|
10483
10606
|
);
|
|
10484
|
-
|
|
10607
|
+
if (bound4) {
|
|
10608
|
+
const stateError = await this.boundStateError(expectedGateHead);
|
|
10609
|
+
if (stateError) return { completed: false, reason: stateError };
|
|
10610
|
+
if (await localBranchHead(this.git, journal.repository.integrationBranch) !== expectedIntegration) {
|
|
10611
|
+
return {
|
|
10612
|
+
completed: false,
|
|
10613
|
+
reason: "Gate changed the frozen integration branch; preserve and inspect it"
|
|
10614
|
+
};
|
|
10615
|
+
}
|
|
10616
|
+
}
|
|
10617
|
+
const receipt = result.artifacts?.find(
|
|
10618
|
+
(path60) => /gate-execution-receipt\.attempt-\d+\.json$/.test(path60)
|
|
10619
|
+
);
|
|
10620
|
+
return result.status === "pass" ? {
|
|
10621
|
+
completed: true,
|
|
10622
|
+
externalId: receipt ?? `${gate.name}:${result.lastAttempt ?? "pass"}`
|
|
10623
|
+
} : { completed: false, reason: result.stderr || `gate ${gate.name} failed` };
|
|
10485
10624
|
}
|
|
10486
10625
|
async ensureIntegrationBranch(branch, targetHeadSha) {
|
|
10487
10626
|
if (await localBranchHead(this.git, branch)) return;
|
|
10488
10627
|
await this.git.checkoutBranch(branch, targetHeadSha);
|
|
10489
10628
|
}
|
|
10629
|
+
async boundStateError(expectedHead) {
|
|
10630
|
+
if (expectedHead && (await this.git.revparse(["HEAD"])).trim() !== expectedHead) {
|
|
10631
|
+
return "Frozen checkout HEAD changed during the operation; preserve and inspect the new commit";
|
|
10632
|
+
}
|
|
10633
|
+
const status = await this.git.status();
|
|
10634
|
+
if (status.files.some(isUnexpectedBoundChange)) {
|
|
10635
|
+
return "Frozen checkout index or working tree changed; preserve and inspect the modifications before continuing";
|
|
10636
|
+
}
|
|
10637
|
+
return null;
|
|
10638
|
+
}
|
|
10490
10639
|
};
|
|
10491
10640
|
function gateForOperation(plan, operation) {
|
|
10492
10641
|
const parts = operation.id.split(":");
|
|
@@ -10532,12 +10681,16 @@ function lastCompletedMergeSha(operations) {
|
|
|
10532
10681
|
function isRunnerStatePath(path60) {
|
|
10533
10682
|
return path60 === ".lexrunner" || path60.startsWith(".lexrunner/") || path60 === "weave-lock.json" || path60.endsWith("/weave-lock.json");
|
|
10534
10683
|
}
|
|
10684
|
+
function isUnexpectedBoundChange(file) {
|
|
10685
|
+
return !(file.index === "?" && file.working_dir === "?" && isRunnerStatePath(file.path));
|
|
10686
|
+
}
|
|
10535
10687
|
|
|
10536
10688
|
// src/commands/weave.ts
|
|
10537
10689
|
import fs22 from "fs";
|
|
10538
10690
|
import path15 from "path";
|
|
10539
10691
|
|
|
10540
10692
|
// src/application/merge-application-service.ts
|
|
10693
|
+
import { join as join16 } from "path";
|
|
10541
10694
|
var MAX_OPERATIONS = 256;
|
|
10542
10695
|
var MAX_LABEL_BYTES2 = 512;
|
|
10543
10696
|
var MAX_RESULT_BYTES2 = 256 * 1024;
|
|
@@ -10564,6 +10717,7 @@ var MergeApplicationService = class {
|
|
|
10564
10717
|
ok: true,
|
|
10565
10718
|
status: "preview",
|
|
10566
10719
|
totalItems: input.plan.items.length,
|
|
10720
|
+
gitInputBinding: input.plan.gitInputs ? "frozen" : "legacy-unbound",
|
|
10567
10721
|
levels,
|
|
10568
10722
|
maxParallelism: levels.length === 0 ? 0 : Math.max(...levels.map((level) => level.length)),
|
|
10569
10723
|
artifactRefs: []
|
|
@@ -10585,10 +10739,10 @@ var MergeApplicationService = class {
|
|
|
10585
10739
|
let runId;
|
|
10586
10740
|
try {
|
|
10587
10741
|
runId = await this.runtime.prepare(input.plan, input.workingDir);
|
|
10588
|
-
} catch {
|
|
10742
|
+
} catch (error) {
|
|
10589
10743
|
throw new MergeApplicationServiceError(
|
|
10590
10744
|
"MERGE_STALE_INPUT",
|
|
10591
|
-
"Merge input could not be bound to current source and target heads"
|
|
10745
|
+
error instanceof FrozenGitInputError ? error.message : "Merge input could not be bound to current source and target heads"
|
|
10592
10746
|
);
|
|
10593
10747
|
}
|
|
10594
10748
|
return this.executePrepared({ plan: input.plan, workingDir: input.workingDir, runId });
|
|
@@ -10618,6 +10772,7 @@ var MergeApplicationService = class {
|
|
|
10618
10772
|
ok: completed,
|
|
10619
10773
|
status: execution.result.ok ? execution.result.outcome : "failed",
|
|
10620
10774
|
totalItems: input.plan.items.length,
|
|
10775
|
+
gitInputBinding: input.plan.gitInputs ? "frozen" : "legacy-unbound",
|
|
10621
10776
|
runId: bound2(input.runId),
|
|
10622
10777
|
operations: execution.operations.map((operation) => ({
|
|
10623
10778
|
id: bound2(operation.id),
|
|
@@ -10638,15 +10793,22 @@ var LocalMergeApplicationRuntime = class {
|
|
|
10638
10793
|
}
|
|
10639
10794
|
async prepare(plan, workingDir) {
|
|
10640
10795
|
const checkpoint = await createLocalResumeCheckpoint({ plan, workingDir });
|
|
10641
|
-
await saveCheckpoint(checkpoint, {
|
|
10796
|
+
await saveCheckpoint(checkpoint, {
|
|
10797
|
+
checkpointDir: join16(workingDir, ".lexrunner", "checkpoints"),
|
|
10798
|
+
skipCleanup: true
|
|
10799
|
+
});
|
|
10642
10800
|
return checkpoint.runId;
|
|
10643
10801
|
}
|
|
10644
10802
|
async resume(runId, workingDir) {
|
|
10645
10803
|
const result = await resumePersistedWeave({
|
|
10646
10804
|
runId,
|
|
10805
|
+
checkpointDir: join16(workingDir, ".lexrunner", "checkpoints"),
|
|
10647
10806
|
driver: new LocalWeaveResumeDriver(workingDir)
|
|
10648
10807
|
});
|
|
10649
|
-
const checkpoint = await loadCheckpoint(runId, {
|
|
10808
|
+
const checkpoint = await loadCheckpoint(runId, {
|
|
10809
|
+
checkpointDir: join16(workingDir, ".lexrunner", "checkpoints"),
|
|
10810
|
+
validatePlanHash: false
|
|
10811
|
+
});
|
|
10650
10812
|
return { result, operations: checkpoint.metadata?.resume?.operations ?? [] };
|
|
10651
10813
|
}
|
|
10652
10814
|
};
|
|
@@ -10959,6 +11121,10 @@ async function runWeaveApply(opts, deps) {
|
|
|
10959
11121
|
console.log(`Items: ${plan.items.length}`);
|
|
10960
11122
|
console.log(`Target: ${plan.target}
|
|
10961
11123
|
`);
|
|
11124
|
+
console.log(
|
|
11125
|
+
`Git inputs: ${preview.summary.gitInputBinding}${plan.gitInputs ? `; acquisition: ${plan.gitInputs.acquisition}` : ""}
|
|
11126
|
+
`
|
|
11127
|
+
);
|
|
10962
11128
|
console.log("\u{1F4E6} Execution Plan\n");
|
|
10963
11129
|
console.log("Execution Order (topological):\n");
|
|
10964
11130
|
levels.forEach((level, index) => {
|
|
@@ -10982,7 +11148,7 @@ async function runWeaveApply(opts, deps) {
|
|
|
10982
11148
|
console.log(`Items: ${plan.items.length}
|
|
10983
11149
|
`);
|
|
10984
11150
|
}
|
|
10985
|
-
if (!opts.skipGates) {
|
|
11151
|
+
if (!opts.skipGates && !(opts.execute && plan.gitInputs)) {
|
|
10986
11152
|
if (!json2) console.log("Running gates...\n");
|
|
10987
11153
|
const executionState = new ExecutionState(plan);
|
|
10988
11154
|
const gatesDir = path15.join(process.cwd(), ".smartergpt", "runner", "gates");
|
|
@@ -11778,7 +11944,7 @@ function registerAutopilotCommand(program2, deps) {
|
|
|
11778
11944
|
argv: process.argv.slice(2)
|
|
11779
11945
|
});
|
|
11780
11946
|
}
|
|
11781
|
-
const { AutopilotLevel0, AutopilotLevel1, AutopilotLevel2: AutopilotLevel22 } = await import("./autopilot-
|
|
11947
|
+
const { AutopilotLevel0, AutopilotLevel1, AutopilotLevel2: AutopilotLevel22 } = await import("./autopilot-7PX6YG55.js");
|
|
11782
11948
|
const context = {
|
|
11783
11949
|
plan,
|
|
11784
11950
|
profilePath: profile.path,
|
|
@@ -11826,7 +11992,7 @@ function registerAutopilotCommand(program2, deps) {
|
|
|
11826
11992
|
// src/application/gate-execution-service.ts
|
|
11827
11993
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
11828
11994
|
import { mkdirSync as mkdirSync12 } from "fs";
|
|
11829
|
-
import { join as
|
|
11995
|
+
import { join as join19, resolve as resolve9 } from "path";
|
|
11830
11996
|
var MAX_ITEMS = 256;
|
|
11831
11997
|
var MAX_GATES_PER_ITEM = 64;
|
|
11832
11998
|
var MAX_LABEL_BYTES3 = 512;
|
|
@@ -11929,7 +12095,7 @@ var GateExecutionService = class {
|
|
|
11929
12095
|
function prepareOwnedArtifactDirectory(requestedRoot) {
|
|
11930
12096
|
const root = resolve9(bounded2(requestedRoot, MAX_ARTIFACT_REF_BYTES));
|
|
11931
12097
|
mkdirSync12(root, { recursive: true });
|
|
11932
|
-
const runDirectory =
|
|
12098
|
+
const runDirectory = join19(root, `gate-run-${Date.now()}-${randomUUID4()}`);
|
|
11933
12099
|
mkdirSync12(runDirectory);
|
|
11934
12100
|
return runDirectory;
|
|
11935
12101
|
}
|
|
@@ -23484,7 +23650,7 @@ var NativeWslProjectionEngine = class {
|
|
|
23484
23650
|
"native publication target became occupied"
|
|
23485
23651
|
);
|
|
23486
23652
|
}
|
|
23487
|
-
const
|
|
23653
|
+
const repositoryIdentity2 = identityOf(staging.repository);
|
|
23488
23654
|
const allocationIdentity = identityOf(staging.allocation);
|
|
23489
23655
|
staging.repository.close();
|
|
23490
23656
|
staging.repository = void 0;
|
|
@@ -23497,7 +23663,7 @@ var NativeWslProjectionEngine = class {
|
|
|
23497
23663
|
staging.repositoryPublished = true;
|
|
23498
23664
|
assertPublishedIdentity(
|
|
23499
23665
|
path35.join(boundary.projectionRoot.path, projectionId),
|
|
23500
|
-
|
|
23666
|
+
repositoryIdentity2,
|
|
23501
23667
|
"native projection"
|
|
23502
23668
|
);
|
|
23503
23669
|
await rename(
|
|
@@ -24383,11 +24549,11 @@ function loadNativeWslProjectionSelection(repositoryRoot, expectedSelectionDiges
|
|
|
24383
24549
|
if (!/^sha256:[a-f0-9]{64}$/u.test(expectedSelectionDigest)) {
|
|
24384
24550
|
throw new Error("Projection selection authority is invalid");
|
|
24385
24551
|
}
|
|
24386
|
-
const
|
|
24552
|
+
const repositoryIdentity2 = captureDirectoryIdentity(
|
|
24387
24553
|
repositoryRoot,
|
|
24388
24554
|
"native projection repository"
|
|
24389
24555
|
);
|
|
24390
|
-
const repository = reopenDirectoryIdentity(
|
|
24556
|
+
const repository = reopenDirectoryIdentity(repositoryIdentity2, "native projection repository");
|
|
24391
24557
|
let gitDirectory;
|
|
24392
24558
|
try {
|
|
24393
24559
|
gitDirectory = openChildDirectory(repository, ".git", "native projection Git directory");
|
|
@@ -44417,7 +44583,7 @@ function createRunManager(baseDirOrOptions) {
|
|
|
44417
44583
|
|
|
44418
44584
|
// src/runs/coordinated-manager.ts
|
|
44419
44585
|
import { existsSync as existsSync24, readFileSync as readFileSync32, renameSync as renameSync3, writeFileSync as writeFileSync19 } from "fs";
|
|
44420
|
-
import { join as
|
|
44586
|
+
import { join as join33 } from "path";
|
|
44421
44587
|
|
|
44422
44588
|
// src/runs/agent-work-supervisor.ts
|
|
44423
44589
|
import { z as z48 } from "zod";
|
|
@@ -51288,7 +51454,7 @@ program.command("view").description("Interactive plan viewer with navigation and
|
|
|
51288
51454
|
program.command("deliverables:list").description("List all autopilot deliverables with metadata").option("--profile-dir <dir>", "Profile directory (default: .smartergpt)").option("--json", "Output JSON format").action(async (opts) => {
|
|
51289
51455
|
try {
|
|
51290
51456
|
const profile = resolveProfile(opts.profileDir);
|
|
51291
|
-
const { DeliverablesManager } = await import("./autopilot-
|
|
51457
|
+
const { DeliverablesManager } = await import("./autopilot-7PX6YG55.js");
|
|
51292
51458
|
const manager = new DeliverablesManager(profile.path);
|
|
51293
51459
|
const deliverables = await manager.listDeliverables();
|
|
51294
51460
|
if (opts.json) {
|
|
@@ -51327,7 +51493,7 @@ program.command("deliverables:list").description("List all autopilot deliverable
|
|
|
51327
51493
|
program.command("deliverables:cleanup").description("Clean up old deliverables based on retention policy").option("--profile-dir <dir>", "Profile directory (default: .smartergpt)").option("--max-age <days>", "Maximum age in days (deletes older deliverables)").option("--max-count <count>", "Maximum number of deliverables to keep").option("--keep-latest", "Always keep the latest deliverables (default: true)", true).option("--dry-run", "Preview cleanup without deleting").option("--json", "Output JSON format").action(async (opts) => {
|
|
51328
51494
|
try {
|
|
51329
51495
|
const profile = resolveProfile(opts.profileDir);
|
|
51330
|
-
const { DeliverablesManager } = await import("./autopilot-
|
|
51496
|
+
const { DeliverablesManager } = await import("./autopilot-7PX6YG55.js");
|
|
51331
51497
|
const manager = new DeliverablesManager(profile.path);
|
|
51332
51498
|
const policy = {
|
|
51333
51499
|
maxAge: opts.maxAge ? parseInt(opts.maxAge) : void 0,
|