@neriros/ralphy 3.10.10 → 3.10.11

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/mcp/index.js CHANGED
@@ -24093,6 +24093,12 @@ var HistoryEntrySchema = exports_external.object({
24093
24093
  appVersion: exports_external.string().optional(),
24094
24094
  usage: IterationUsageSchema.partial().optional()
24095
24095
  });
24096
+ var RevisionSchema = exports_external.object({
24097
+ version: exports_external.number(),
24098
+ attachmentId: exports_external.string(),
24099
+ sha256: exports_external.string(),
24100
+ trigger: exports_external.string()
24101
+ });
24096
24102
  var StateSchema = exports_external.object({
24097
24103
  version: exports_external.literal("2"),
24098
24104
  name: exports_external.string(),
@@ -24141,12 +24147,16 @@ var StateSchema = exports_external.object({
24141
24147
  attachmentId: exports_external.string().nullable().default(null),
24142
24148
  sha256: exports_external.string().nullable().default(null)
24143
24149
  }).default({ attachmentId: null, sha256: null }),
24150
+ designRevisions: exports_external.array(RevisionSchema).default([]),
24151
+ designPdfRevisions: exports_external.array(RevisionSchema).default([]),
24144
24152
  legacyProposalPurged: exports_external.boolean().default(false)
24145
24153
  }).default({
24146
24154
  proposal: { attachmentId: null, sha256: null },
24147
24155
  design: { attachmentId: null, sha256: null },
24148
24156
  proposalPdf: { attachmentId: null, sha256: null },
24149
24157
  designPdf: { attachmentId: null, sha256: null },
24158
+ designRevisions: [],
24159
+ designPdfRevisions: [],
24150
24160
  legacyProposalPurged: false
24151
24161
  }),
24152
24162
  confirmation: exports_external.object({
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
18928
18928
  import { resolve } from "path";
18929
18929
  function getVersion() {
18930
18930
  try {
18931
- if ("3.10.10")
18932
- return "3.10.10";
18931
+ if ("3.10.11")
18932
+ return "3.10.11";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -89590,7 +89590,7 @@ var init_zod2 = __esm(() => {
89590
89590
  function markersOf(set3) {
89591
89591
  return Array.isArray(set3) ? set3 : [set3];
89592
89592
  }
89593
- var IterationUsageSchema, UsageSchema, HistoryEntrySchema, StateSchema, PhaseFrontmatterSchema;
89593
+ var IterationUsageSchema, UsageSchema, HistoryEntrySchema, RevisionSchema, StateSchema, PhaseFrontmatterSchema;
89594
89594
  var init_types2 = __esm(() => {
89595
89595
  init_zod2();
89596
89596
  IterationUsageSchema = exports_external2.object({
@@ -89623,6 +89623,12 @@ var init_types2 = __esm(() => {
89623
89623
  appVersion: exports_external2.string().optional(),
89624
89624
  usage: IterationUsageSchema.partial().optional()
89625
89625
  });
89626
+ RevisionSchema = exports_external2.object({
89627
+ version: exports_external2.number(),
89628
+ attachmentId: exports_external2.string(),
89629
+ sha256: exports_external2.string(),
89630
+ trigger: exports_external2.string()
89631
+ });
89626
89632
  StateSchema = exports_external2.object({
89627
89633
  version: exports_external2.literal("2"),
89628
89634
  name: exports_external2.string(),
@@ -89671,12 +89677,16 @@ var init_types2 = __esm(() => {
89671
89677
  attachmentId: exports_external2.string().nullable().default(null),
89672
89678
  sha256: exports_external2.string().nullable().default(null)
89673
89679
  }).default({ attachmentId: null, sha256: null }),
89680
+ designRevisions: exports_external2.array(RevisionSchema).default([]),
89681
+ designPdfRevisions: exports_external2.array(RevisionSchema).default([]),
89674
89682
  legacyProposalPurged: exports_external2.boolean().default(false)
89675
89683
  }).default({
89676
89684
  proposal: { attachmentId: null, sha256: null },
89677
89685
  design: { attachmentId: null, sha256: null },
89678
89686
  proposalPdf: { attachmentId: null, sha256: null },
89679
89687
  designPdf: { attachmentId: null, sha256: null },
89688
+ designRevisions: [],
89689
+ designPdfRevisions: [],
89680
89690
  legacyProposalPurged: false
89681
89691
  }),
89682
89692
  confirmation: exports_external2.object({
@@ -105470,6 +105480,7 @@ class AgentCoordinator {
105470
105480
  opts;
105471
105481
  workers = [];
105472
105482
  pendingIds = new Set;
105483
+ inFlight = new Set;
105473
105484
  queue = [];
105474
105485
  stopped = false;
105475
105486
  paused = null;
@@ -106036,7 +106047,31 @@ class AgentCoordinator {
106036
106047
  while (this.workers.length + this.pendingIds.size < this.opts.concurrency && this.queue.length > 0) {
106037
106048
  const next = this.queue.shift();
106038
106049
  this.pendingIds.add(next.issue.id);
106039
- this.launchWorker(next.issue, next.trigger, next.mention);
106050
+ this.track(this.launchWorker(next.issue, next.trigger, next.mention));
106051
+ }
106052
+ }
106053
+ track(p) {
106054
+ this.inFlight.add(p);
106055
+ p.finally(() => {
106056
+ this.inFlight.delete(p);
106057
+ });
106058
+ return p;
106059
+ }
106060
+ async whenSettled() {
106061
+ let consecutiveEmpty = 0;
106062
+ for (let guard = 0;guard < 1000; guard++) {
106063
+ if (this.inFlight.size > 0) {
106064
+ await Promise.allSettled(this.inFlight);
106065
+ consecutiveEmpty = 0;
106066
+ }
106067
+ await new Promise((r) => setTimeout(r, 0));
106068
+ if (this.inFlight.size === 0) {
106069
+ consecutiveEmpty += 1;
106070
+ if (consecutiveEmpty >= WHEN_SETTLED_STABLE_HOPS)
106071
+ return;
106072
+ } else {
106073
+ consecutiveEmpty = 0;
106074
+ }
106040
106075
  }
106041
106076
  }
106042
106077
  async launchWorker(issue2, trigger, mention) {
@@ -106154,7 +106189,10 @@ class AgentCoordinator {
106154
106189
  this.deps.onLog(`! sync-tasks (launch) failed for ${issue2.identifier}: ${err.message}`, "yellow");
106155
106190
  }
106156
106191
  }
106157
- handle.exited.then(async (code) => {
106192
+ handle.exited.then((code) => this.track(this.finalizeWorkerExit(worker, issue2, prep, trigger, code)));
106193
+ }
106194
+ async finalizeWorkerExit(worker, issue2, prep, trigger, code) {
106195
+ {
106158
106196
  const idx = this.workers.indexOf(worker);
106159
106197
  if (idx >= 0)
106160
106198
  this.workers.splice(idx, 1);
@@ -106202,7 +106240,7 @@ class AgentCoordinator {
106202
106240
  await this.notifyExited(issue2, prep.changeName, code, trigger);
106203
106241
  this.deps.onWorkersChanged();
106204
106242
  this.spawnNext();
106205
- });
106243
+ }
106206
106244
  }
106207
106245
  async restartWorker(changeName) {
106208
106246
  if (this.stopped)
@@ -106378,7 +106416,7 @@ var emptyPrStatus = () => ({
106378
106416
  conflicted: 0,
106379
106417
  ciFailed: 0,
106380
106418
  quarantined: 0
106381
- }), emptyPollResult = () => ({
106419
+ }), WHEN_SETTLED_STABLE_HOPS = 3, emptyPollResult = () => ({
106382
106420
  found: 0,
106383
106421
  added: 0,
106384
106422
  buckets: {
@@ -107521,6 +107559,11 @@ var init_linear_resolvers = __esm(() => {
107521
107559
  // apps/agent/src/agent/wire/prepare.ts
107522
107560
  import { mkdir as mkdir8 } from "fs/promises";
107523
107561
  import { join as join27 } from "path";
107562
+ function composeAppendPrompt(promptArg, cfgAppendPrompt, workflowPrompt) {
107563
+ return [promptArg || cfgAppendPrompt || "", workflowPrompt].filter(Boolean).join(`
107564
+
107565
+ `);
107566
+ }
107524
107567
  function createPrepareHelpers(input) {
107525
107568
  const {
107526
107569
  args,
@@ -107617,9 +107660,7 @@ function createPrepareHelpers(input) {
107617
107660
  } catch (err) {
107618
107661
  diag("workflow", `! workflow render failed: ${err.message}`, "yellow");
107619
107662
  }
107620
- const appendPrompt = [args.prompt || cfg.appendPrompt || "", workflowPrompt].filter(Boolean).join(`
107621
-
107622
- `);
107663
+ const appendPrompt = composeAppendPrompt(args.prompt ?? "", cfg.appendPrompt ?? "", workflowPrompt);
107623
107664
  changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue2, comments, appendPrompt, attachments);
107624
107665
  } else {
107625
107666
  changeName = derivedName;
@@ -108620,6 +108661,103 @@ function buildTicketDigest(issue2, comments) {
108620
108661
  function retroDepEntry(agentDebug, hook) {
108621
108662
  return agentDebug ? { runRetrospective: hook } : {};
108622
108663
  }
108664
+ function computeWantPr(wantPrBase, isAwaiting, isAwaitingConfirmation) {
108665
+ return wantPrBase && !isAwaiting && !isAwaitingConfirmation;
108666
+ }
108667
+ function computeWantValidateOnly(hasValidateSpec, wantPrBase) {
108668
+ return hasValidateSpec && !wantPrBase;
108669
+ }
108670
+ function releaseWorkerMaps(maps, changeName) {
108671
+ maps.cwdByChange.delete(changeName);
108672
+ maps.statesDirByChange.delete(changeName);
108673
+ maps.branchByChange.delete(changeName);
108674
+ maps.issueByChange.delete(changeName);
108675
+ }
108676
+ function buildTaskCmd(args, cfg, changeName) {
108677
+ const engine = args.engineSet ? args.engine : cfg.engine;
108678
+ const model = args.engineSet ? args.model : cfg.model;
108679
+ const c = [
108680
+ process.execPath,
108681
+ process.argv[1] ?? "",
108682
+ "loop",
108683
+ "task",
108684
+ "--name",
108685
+ changeName,
108686
+ "--" + engine,
108687
+ "--model",
108688
+ model
108689
+ ];
108690
+ const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
108691
+ if (maxIter > 0)
108692
+ c.push("--max-iterations", String(maxIter));
108693
+ const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
108694
+ if (maxCost > 0)
108695
+ c.push("--max-cost", String(maxCost));
108696
+ const maxRuntime = args.maxRuntimeMinutes || cfg.maxRuntimeMinutesPerTask;
108697
+ if (maxRuntime > 0)
108698
+ c.push("--max-runtime", String(maxRuntime));
108699
+ const maxFailures = args.maxConsecutiveFailures !== 5 ? args.maxConsecutiveFailures : cfg.maxConsecutiveFailuresPerTask;
108700
+ if (maxFailures !== 5)
108701
+ c.push("--max-failures", String(maxFailures));
108702
+ const delay2 = args.delay || cfg.iterationDelaySeconds;
108703
+ if (delay2 > 0)
108704
+ c.push("--delay", String(delay2));
108705
+ if (args.log || cfg.logRawStream)
108706
+ c.push("--log");
108707
+ if (args.verbose || cfg.taskVerbose)
108708
+ c.push("--verbose");
108709
+ if (args.manualTest || cfg.enableManualTest)
108710
+ c.push("--manual-test");
108711
+ const rp = cfg.openspec.reviewPhase;
108712
+ if (rp.enabled) {
108713
+ c.push("--review-enabled");
108714
+ if (rp.maxRounds !== 1)
108715
+ c.push("--review-max-rounds", String(rp.maxRounds));
108716
+ if (rp.reviewerModel !== undefined)
108717
+ c.push("--review-model", rp.reviewerModel);
108718
+ if (rp.reviewerContextStrategy !== "fresh")
108719
+ c.push("--review-context-strategy", rp.reviewerContextStrategy);
108720
+ }
108721
+ c.push("--from-agent");
108722
+ return c;
108723
+ }
108724
+ function buildPostTaskInput(input) {
108725
+ const { args, cfg } = input;
108726
+ return {
108727
+ ...input.trigger ? { mode: input.trigger } : {},
108728
+ ...input.prUrl ? { prUrl: input.prUrl } : {},
108729
+ changeName: input.changeName,
108730
+ cwd: input.cwd,
108731
+ projectRoot: input.projectRoot,
108732
+ changeDir: input.changeDir,
108733
+ stateFilePath: input.stateFilePath,
108734
+ branch: input.branch,
108735
+ issue: input.issue,
108736
+ exitCode: input.exitCode,
108737
+ useWorktree: input.useWorktree,
108738
+ wantPr: input.wantPr,
108739
+ wantFixCi: input.wantFixCi,
108740
+ wantAutoMerge: input.wantAutoMerge,
108741
+ wantValidateOnly: input.wantValidateOnly,
108742
+ cfg: {
108743
+ teardownScript: cfg.teardownScript ?? null,
108744
+ prBaseBranch: cfg.prBaseBranch,
108745
+ autoMergeStrategy: cfg.autoMergeStrategy,
108746
+ maxCiFixAttempts: cfg.maxCiFixAttempts,
108747
+ ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
108748
+ cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
108749
+ ignoreCiChecks: cfg.ignoreCiChecks,
108750
+ stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
108751
+ neverTouch: cfg.boundaries.never_touch,
108752
+ metaOnlyFiles: cfg.boundaries.meta_only_files,
108753
+ finalizeNoOpAsDone: cfg.finalizeNoOpAsDone,
108754
+ manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled,
108755
+ prDraft: cfg.prDraft,
108756
+ validateCommands: [cfg.commands.test, cfg.commands.lint, cfg.commands.typecheck].filter((c) => Boolean(c))
108757
+ },
108758
+ respawnWorker: input.respawnWorker
108759
+ };
108760
+ }
108623
108761
  function createSpawnWorker(input) {
108624
108762
  const {
108625
108763
  args,
@@ -108652,54 +108790,8 @@ function createSpawnWorker(input) {
108652
108790
  onWorkerOutput,
108653
108791
  onWorkerCmd
108654
108792
  } = input;
108655
- function buildTaskCmdFor(changeName) {
108656
- const engine = args.engineSet ? args.engine : cfg.engine;
108657
- const model = args.engineSet ? args.model : cfg.model;
108658
- const c = [
108659
- process.execPath,
108660
- process.argv[1] ?? "",
108661
- "loop",
108662
- "task",
108663
- "--name",
108664
- changeName,
108665
- "--" + engine,
108666
- "--model",
108667
- model
108668
- ];
108669
- const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
108670
- if (maxIter > 0)
108671
- c.push("--max-iterations", String(maxIter));
108672
- const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
108673
- if (maxCost > 0)
108674
- c.push("--max-cost", String(maxCost));
108675
- const maxRuntime = args.maxRuntimeMinutes || cfg.maxRuntimeMinutesPerTask;
108676
- if (maxRuntime > 0)
108677
- c.push("--max-runtime", String(maxRuntime));
108678
- const maxFailures = args.maxConsecutiveFailures !== 5 ? args.maxConsecutiveFailures : cfg.maxConsecutiveFailuresPerTask;
108679
- if (maxFailures !== 5)
108680
- c.push("--max-failures", String(maxFailures));
108681
- const delay2 = args.delay || cfg.iterationDelaySeconds;
108682
- if (delay2 > 0)
108683
- c.push("--delay", String(delay2));
108684
- if (args.log || cfg.logRawStream)
108685
- c.push("--log");
108686
- if (args.verbose || cfg.taskVerbose)
108687
- c.push("--verbose");
108688
- if (args.manualTest || cfg.enableManualTest)
108689
- c.push("--manual-test");
108690
- const rp = cfg.openspec.reviewPhase;
108691
- if (rp.enabled) {
108692
- c.push("--review-enabled");
108693
- if (rp.maxRounds !== 1)
108694
- c.push("--review-max-rounds", String(rp.maxRounds));
108695
- if (rp.reviewerModel !== undefined)
108696
- c.push("--review-model", rp.reviewerModel);
108697
- if (rp.reviewerContextStrategy !== "fresh")
108698
- c.push("--review-context-strategy", rp.reviewerContextStrategy);
108699
- }
108700
- c.push("--from-agent");
108701
- return c;
108702
- }
108793
+ const doPostTask = input.runners?.runPostTask ?? runPostTask;
108794
+ const buildTaskCmdFor = (changeName) => buildTaskCmd(args, cfg, changeName);
108703
108795
  const retroSeen = new Set;
108704
108796
  const runRetrospectiveHook = async (info) => {
108705
108797
  try {
@@ -108777,7 +108869,7 @@ function createSpawnWorker(input) {
108777
108869
  const workerLayout = projectLayout(cwd2);
108778
108870
  const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
108779
108871
  const hasValidateSpec = await Bun.file(validateSpecPath).exists();
108780
- const wantValidateOnly = hasValidateSpec && !wantPrBase;
108872
+ const wantValidateOnly = computeWantValidateOnly(hasValidateSpec, wantPrBase);
108781
108873
  if (hasValidateSpec) {
108782
108874
  try {
108783
108875
  const stateFile = workerLayout.stateFile(changeName);
@@ -108821,10 +108913,10 @@ function createSpawnWorker(input) {
108821
108913
  } catch (err) {
108822
108914
  diag("tasks", `! tasks.md normalization failed: ${err.message}`, "yellow");
108823
108915
  }
108824
- const wantPr = wantPrBase && !awaitingChangeSet.has(changeName) && !(coordRef.current?.isAwaitingConfirmation(changeName) ?? false);
108825
- const effectiveCode = await runPostTask({
108826
- ...trigger ? { mode: trigger } : {},
108827
- ...prByChange?.get(changeName) ? { prUrl: prByChange.get(changeName) } : {},
108916
+ const wantPr = computeWantPr(wantPrBase, awaitingChangeSet.has(changeName), coordRef.current?.isAwaitingConfirmation(changeName) ?? false);
108917
+ const effectiveCode = await doPostTask(buildPostTaskInput({
108918
+ args,
108919
+ cfg,
108828
108920
  changeName,
108829
108921
  cwd: cwd2,
108830
108922
  projectRoot,
@@ -108838,24 +108930,10 @@ function createSpawnWorker(input) {
108838
108930
  wantFixCi,
108839
108931
  wantAutoMerge,
108840
108932
  wantValidateOnly,
108841
- cfg: {
108842
- teardownScript: cfg.teardownScript ?? null,
108843
- prBaseBranch: cfg.prBaseBranch,
108844
- autoMergeStrategy: cfg.autoMergeStrategy,
108845
- maxCiFixAttempts: cfg.maxCiFixAttempts,
108846
- ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
108847
- cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
108848
- ignoreCiChecks: cfg.ignoreCiChecks,
108849
- stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
108850
- neverTouch: cfg.boundaries.never_touch,
108851
- metaOnlyFiles: cfg.boundaries.meta_only_files,
108852
- finalizeNoOpAsDone: cfg.finalizeNoOpAsDone,
108853
- manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled,
108854
- prDraft: cfg.prDraft,
108855
- validateCommands: [cfg.commands.test, cfg.commands.lint, cfg.commands.typecheck].filter((c) => Boolean(c))
108856
- },
108933
+ ...trigger ? { trigger } : {},
108934
+ ...prByChange?.get(changeName) ? { prUrl: prByChange.get(changeName) } : {},
108857
108935
  respawnWorker: respawn
108858
- }, {
108936
+ }), {
108859
108937
  cmd: tracedCmd,
108860
108938
  git: gitRunner,
108861
108939
  log: onLog,
@@ -108894,10 +108972,7 @@ function createSpawnWorker(input) {
108894
108972
  },
108895
108973
  resolveDependencyBaseBranch: (issue2) => resolveDependencyBaseBranchImpl(issue2, tracedCmd, cwd2, { apiKey, onLog })
108896
108974
  });
108897
- cwdByChange.delete(changeName);
108898
- statesDirByChange.delete(changeName);
108899
- branchByChange.delete(changeName);
108900
- issueByChange.delete(changeName);
108975
+ releaseWorkerMaps({ cwdByChange, statesDirByChange, branchByChange, issueByChange }, changeName);
108901
108976
  onWorkerExited(changeName);
108902
108977
  return effectiveCode;
108903
108978
  });
@@ -261725,6 +261800,25 @@ async function readSpecAttachmentsSubtree(statePath) {
261725
261800
  const sidecar = await readSlotSidecar(dirname13(statePath), "specAttachments");
261726
261801
  return sidecar ?? await readInlineSpecAttachments(statePath);
261727
261802
  }
261803
+ function asRevisions(value) {
261804
+ if (!Array.isArray(value))
261805
+ return [];
261806
+ const out = [];
261807
+ for (const entry of value) {
261808
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
261809
+ const e = entry;
261810
+ if (typeof e.version === "number" && typeof e.attachmentId === "string" && typeof e.sha256 === "string" && typeof e.trigger === "string") {
261811
+ out.push({
261812
+ version: e.version,
261813
+ attachmentId: e.attachmentId,
261814
+ sha256: e.sha256,
261815
+ trigger: e.trigger
261816
+ });
261817
+ }
261818
+ }
261819
+ }
261820
+ return out;
261821
+ }
261728
261822
  async function readSpecAttachments(statePath) {
261729
261823
  const sa = await readSpecAttachmentsSubtree(statePath);
261730
261824
  return {
@@ -261743,12 +261837,45 @@ async function readSpecAttachments(statePath) {
261743
261837
  designPdf: {
261744
261838
  attachmentId: sa.designPdf?.attachmentId ?? null,
261745
261839
  sha256: sa.designPdf?.sha256 ?? null
261746
- }
261840
+ },
261841
+ designRevisions: asRevisions(sa.designRevisions),
261842
+ designPdfRevisions: asRevisions(sa.designPdfRevisions)
261747
261843
  };
261748
261844
  }
261749
261845
  async function persistSlot(statePath, slot, value) {
261750
261846
  await writeField(stateDirOf(statePath), "linear-attachments", `specAttachments.${slot}`, value);
261751
261847
  }
261848
+ async function persistRevision(statePath, slot, revisions) {
261849
+ await writeField(stateDirOf(statePath), "linear-attachments", `specAttachments.${REVISIONS_KEY[slot]}`, revisions);
261850
+ }
261851
+ async function isDesignSealed(stateDir) {
261852
+ try {
261853
+ const pr2 = await readSlotSidecar(stateDir, "pr");
261854
+ const url2 = pr2?.url;
261855
+ if (typeof url2 === "string" && url2.length > 0)
261856
+ return true;
261857
+ } catch {}
261858
+ try {
261859
+ const confirmation = await readSlotSidecar(stateDir, "confirmation");
261860
+ if (confirmation?.earlyDraftPrAt != null)
261861
+ return true;
261862
+ } catch {}
261863
+ return false;
261864
+ }
261865
+ async function resolveTriggerLabel(stateDir) {
261866
+ try {
261867
+ const flow2 = await readSlotSidecar(stateDir, "flow");
261868
+ const snapshot = flow2?.actorSnapshot;
261869
+ const value = snapshot?.value;
261870
+ if (typeof value === "string" && TRIGGER_LABELS[value])
261871
+ return TRIGGER_LABELS[value];
261872
+ } catch {}
261873
+ return "revision";
261874
+ }
261875
+ function versionedTitle(slot, n, label) {
261876
+ const base2 = `Ralph design #${n} (${label})`;
261877
+ return slot === "designPdf" ? `${base2} (PDF)` : base2;
261878
+ }
261752
261879
  async function adopt(deps, slot) {
261753
261880
  const spec = SLOT_SPECS[slot];
261754
261881
  try {
@@ -261793,6 +261920,65 @@ function extractImplementationSection(tasksMarkdown) {
261793
261920
  return captured.join(`
261794
261921
  `).trim();
261795
261922
  }
261923
+ async function syncSlotSealed(deps, slot, sourceBytes, hash2, state) {
261924
+ const spec = SLOT_SPECS[slot];
261925
+ const revisions = state[REVISIONS_KEY[slot]];
261926
+ const v1Sha = state[slot]?.sha256 ?? null;
261927
+ if (hash2 === v1Sha || revisions.some((r) => r.sha256 === hash2)) {
261928
+ deps.log(` spec-attachments: ${spec.uploadFilename} unchanged (sealed), skipping`, "gray");
261929
+ return;
261930
+ }
261931
+ const n = 2 + revisions.length;
261932
+ const label = await resolveTriggerLabel(stateDirOf(deps.statePath));
261933
+ const title = versionedTitle(slot, n, label);
261934
+ let uploadBytes;
261935
+ try {
261936
+ uploadBytes = await spec.renderBytes(sourceBytes);
261937
+ } catch (err) {
261938
+ deps.log(`! spec-attachments: render ${spec.uploadFilename} (sealed) failed: ${err.message}`, "yellow");
261939
+ return;
261940
+ }
261941
+ let assetUrl;
261942
+ try {
261943
+ const uploaded = await deps.mutations.uploadFileToLinear(deps.apiKey, {
261944
+ filename: spec.uploadFilename,
261945
+ contentType: spec.contentType,
261946
+ bytes: uploadBytes
261947
+ });
261948
+ assetUrl = uploaded.assetUrl;
261949
+ } catch (err) {
261950
+ deps.log(`! spec-attachments: upload ${spec.uploadFilename} (sealed) failed: ${describeLinearError(err)}`, "yellow");
261951
+ return;
261952
+ }
261953
+ let attachmentId = null;
261954
+ try {
261955
+ attachmentId = await deps.mutations.findIssueAttachmentByTitle(deps.apiKey, deps.issueId, title);
261956
+ if (attachmentId) {
261957
+ deps.log(` spec-attachments: adopted existing ${title} attachment ${attachmentId}`, "gray");
261958
+ }
261959
+ } catch (err) {
261960
+ deps.log(`! spec-attachments: findIssueAttachmentByTitle ${title} failed (treating as no match): ${describeLinearError(err)}`, "yellow");
261961
+ attachmentId = null;
261962
+ }
261963
+ if (!attachmentId) {
261964
+ try {
261965
+ attachmentId = await deps.mutations.createAttachmentForUrl(deps.apiKey, {
261966
+ issueId: deps.issueId,
261967
+ url: assetUrl,
261968
+ title,
261969
+ subtitle: `iteration ${deps.iteration}`
261970
+ });
261971
+ } catch (err) {
261972
+ deps.log(`! spec-attachments: createAttachmentForUrl ${title} failed: ${describeLinearError(err)}`, "yellow");
261973
+ return;
261974
+ }
261975
+ deps.log(` spec-attachments: created ${title} attachment`, "gray");
261976
+ }
261977
+ await persistRevision(deps.statePath, slot, [
261978
+ ...revisions,
261979
+ { version: n, attachmentId, sha256: hash2, trigger: label }
261980
+ ]);
261981
+ }
261796
261982
  async function syncSlot(deps, slot) {
261797
261983
  const spec = SLOT_SPECS[slot];
261798
261984
  const [primaryName, ...trailingNames] = spec.sourceFiles;
@@ -261848,7 +262034,12 @@ ${body}
261848
262034
  offset += p.length;
261849
262035
  }
261850
262036
  const hash2 = sha256Hex(sourceBytes);
261851
- let current = (await readSpecAttachments(deps.statePath))[slot] ?? EMPTY_SLOT;
262037
+ const state = await readSpecAttachments(deps.statePath);
262038
+ if (await isDesignSealed(stateDirOf(deps.statePath))) {
262039
+ await syncSlotSealed(deps, slot, sourceBytes, hash2, state);
262040
+ return;
262041
+ }
262042
+ let current = state[slot] ?? EMPTY_SLOT;
261852
262043
  if (!current.attachmentId) {
261853
262044
  const { adoptedId } = await adopt(deps, slot);
261854
262045
  if (adoptedId) {
@@ -261950,7 +262141,7 @@ async function syncSpecAttachments(deps) {
261950
262141
  await syncSlot(deps, slot);
261951
262142
  }
261952
262143
  }
261953
- var identityRender = async (b2) => b2, pdfRender = (title) => async (b2) => renderMarkdownToPdf(new TextDecoder().decode(b2), title), SLOT_SPECS, LEGACY_SLOT_TITLES, EMPTY_SLOT;
262144
+ var identityRender = async (b2) => b2, pdfRender = (title) => async (b2) => renderMarkdownToPdf(new TextDecoder().decode(b2), title), SLOT_SPECS, LEGACY_SLOT_TITLES, REVISIONS_KEY, EMPTY_SLOT, TRIGGER_LABELS;
261954
262145
  var init_spec_attachments = __esm(() => {
261955
262146
  init_store();
261956
262147
  init_comment_sync();
@@ -261977,7 +262168,16 @@ var init_spec_attachments = __esm(() => {
261977
262168
  proposal: "Ralph proposal",
261978
262169
  proposalPdf: "Ralph proposal (PDF)"
261979
262170
  };
262171
+ REVISIONS_KEY = {
262172
+ design: "designRevisions",
262173
+ designPdf: "designPdfRevisions"
262174
+ };
261980
262175
  EMPTY_SLOT = { attachmentId: null, sha256: null };
262176
+ TRIGGER_LABELS = {
262177
+ review: "review follow-up",
262178
+ "ci-fix": "CI fix",
262179
+ "conflict-fix": "conflict fix"
262180
+ };
261981
262181
  });
261982
262182
 
261983
262183
  // apps/agent/src/agent/wire/comment-sync.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "3.10.10",
3
+ "version": "3.10.11",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",