@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/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-SHSL643G.js";
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.1.0",
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/Guffawaffle/lexrunner.git"
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.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
- const planItems = prDetails.map((pr) => transformPRToPlanItem(pr, options));
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.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
- return dep.includes("#");
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
- switch (gateName) {
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: getGateCommand(gateName),
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: getGateArtifacts(gateName),
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
- const sourceHeads = {};
10336
- for (const item of input.plan.items) {
10337
- sourceHeads[item.name] = await exactRefHead(git2, item.name);
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((file) => !isRunnerStatePath(file.path));
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
- const targetHead = await exactRefHead(this.git, journal.repository.target);
10391
- if (targetHead !== journal.repository.targetHeadSha) {
10392
- return { valid: false, reason: "target branch changed after the checkpoint was created" };
10393
- }
10394
- for (const [name, expected] of Object.entries(journal.repository.sourceHeads)) {
10395
- if (await exactRefHead(this.git, name) !== expected) {
10396
- return {
10397
- valid: false,
10398
- reason: `source branch ${name} changed after checkpoint creation`
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
- await this.git.checkout(`origin/${operation.item}`).catch(() => this.git.checkout(operation.item));
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 artifactDir = join15(this.workingDir, ".lexrunner", "runs", checkpoint.runId, "gates");
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
- return result.status === "pass" ? { completed: true, externalId: `${gate.name}:${result.lastAttempt ?? "pass"}` } : { completed: false, reason: result.stderr || `gate ${gate.name} failed` };
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, { skipCleanup: true });
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, { validatePlanHash: false });
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-JWIOXBCZ.js");
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 join18, resolve as resolve9 } from "path";
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 = join18(root, `gate-run-${Date.now()}-${randomUUID4()}`);
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 repositoryIdentity = identityOf(staging.repository);
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
- repositoryIdentity,
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 repositoryIdentity = captureDirectoryIdentity(
24552
+ const repositoryIdentity2 = captureDirectoryIdentity(
24387
24553
  repositoryRoot,
24388
24554
  "native projection repository"
24389
24555
  );
24390
- const repository = reopenDirectoryIdentity(repositoryIdentity, "native projection repository");
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 join32 } from "path";
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-JWIOXBCZ.js");
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-JWIOXBCZ.js");
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,