@uchibeke/gallivant 0.2.0 → 0.2.1

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/lib/tick.mjs CHANGED
@@ -20,7 +20,7 @@ import { ROUND_MARKER, NUDGE_MARKER, READY_LABEL, NEEDS_LABEL, actor, normalizeL
20
20
  import { publicPrViolations, publicCommentViolations, publicCorrection, publicReviewHash } from "./public_output.mjs";
21
21
  import { hasLegacyStatusLabels } from "./public-format.mjs";
22
22
  import { adapterFor, buildRun, capabilitiesFor } from "./harness.mjs";
23
- import { executionRunner, unsubmittedInitialDispatch } from "./execution.mjs";
23
+ import { executionRunner, reserveExecutionEpoch, unsubmittedInitialDispatch } from "./execution.mjs";
24
24
  import { reconcileExecutionResult, executionResultCheckpoint, requireVerificationSandbox } from "./execution_result.mjs";
25
25
  import { readJson } from "./fs.mjs";
26
26
  import { join } from "node:path";
@@ -453,6 +453,16 @@ export function makeTick(deps) {
453
453
  prompt = `THE BRIEF HAS CHANGED. Reread the brief and its carried files in the worktree.\n${entry.brief_changes.join("\n")}\n\n${prompt}`;
454
454
  }
455
455
  const execution = runnerFor(id), remote = execution.capabilities?.transport === "remote";
456
+ if (execution.ledgerEpoch > 0) reserveExecutionEpoch({ root: execution.root, taskId: id,
457
+ provider: entry.harness, account: execution.account, epoch: execution.ledgerEpoch, pr: entry.pr });
458
+ // A registration made by older code can be recognized from its exact settled
459
+ // old ledger without modifying that ledger. Persist the chosen new epoch
460
+ // before the first launch changes `last` and `execution`, which are the
461
+ // evidence the read-only recovery rule needs on later process starts.
462
+ if (execution.ledgerEpoch > 0 && entry.execution_epoch == null) {
463
+ entry = state.update(id, { execution_epoch: execution.ledgerEpoch,
464
+ execution_handoff: { from: entry.execution?.provider, to: entry.harness, previousRun: entry.last?.run ?? null, at: now() } });
465
+ }
456
466
  const mode = entry.session ? "resume" : "start";
457
467
  const session = entry.session || entry.learning_continuity?.newSession || (remote ? null : adapterFor(entry.harness).newSession());
458
468
  // Extra read-only directories, resolved in one place (lib/config.mjs) from the operator's config list plus the
@@ -924,9 +934,16 @@ export function makeTick(deps) {
924
934
  // would undo the record that was just made.
925
935
  if (merged?.merged) entry = state.get(id) ?? entry;
926
936
  const fix = { repo: entry.repo, pr: entry.pr, headSha: pr.headRefOid, events, receipt: receiptFor(id), denials, merged: merged?.merged ? merged : null };
937
+ const previousHarness = entry.execution_handoff?.from ?? (entry.execution?.provider !== entry.harness ? entry.execution?.provider : null);
938
+ // Recovery from a lost registry rebuilds the entry from the PR block, so
939
+ // neither of those fields survives. The durable ledger epoch still proves
940
+ // this is a cross-harness takeover and calls for the same context prompt.
941
+ const handoff = !entry.session && (previousHarness || runnerFor(id).ledgerEpoch > 0)
942
+ ? `The operator switched this PR from ${previousHarness || "a previous harness"} to ${entry.harness}. This is a new provider session; the previous conversation is unavailable. Before editing, read the approved brief${entry.prd ? ` at ${entry.prd}` : " if one is recorded"}, its carried files, the PR and review threads, and the local checkout's status and unpushed commits. Preserve existing work and review unresolved feedback. The brief's original harness field records its starting route; this explicit PR reset selected ${entry.harness} for subsequent work.`
943
+ : null;
927
944
  const prompt = entry.session
928
945
  ? fixPrompt({ ...fix, verify: verifyFor(id) })
929
- : `${registerPrompt({ repo: entry.repo, pr: entry.pr })}\n\n${fixPrompt({ ...fix, verify: [] })}`;
946
+ : `${registerPrompt({ repo: entry.repo, pr: entry.pr })}${handoff ? `\n\n${handoff}` : ""}\n\n${fixPrompt({ ...fix, verify: [] })}`;
930
947
  // The invariant: a spawned run must be recorded before anything else can fail. Everything that can throw goes
931
948
  // above the spawn, and the state that owns the run is saved immediately below it with no await in between. A
932
949
  // process running that the registry does not know about is the worst state this function can leave: `settleRun`
@@ -1152,6 +1169,8 @@ export function makeTick(deps) {
1152
1169
  if (await cancelControl(deps, id)) return;
1153
1170
  if (!settleOnly && await recoverControl(deps, id)) return;
1154
1171
  const execution = runnerFor(id);
1172
+ if (execution.ledgerEpoch > 0) reserveExecutionEpoch({ root: execution.root, taskId: id,
1173
+ provider: entry.harness, account: execution.account, epoch: execution.ledgerEpoch, pr: entry.pr });
1155
1174
  if (execution.reconcile) {
1156
1175
  if (!skipReconciliation) await execution.reconcile({ force: startupReconciliation.delete(id) });
1157
1176
  const pending = execution.current();
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@uchibeke/gallivant",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@uchibeke/gallivant",
9
- "version": "0.2.0",
9
+ "version": "0.2.1",
10
10
  "bundleDependencies": [
11
11
  "smol-toml"
12
12
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uchibeke/gallivant",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Turn repository tasks into reviewed pull requests with local or hosted agents, acceptance checks and a persistent review loop.",
5
5
  "type": "module",
6
6
  "bin": {