@neriros/ralphy 3.10.2 → 3.10.4

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.
Files changed (2) hide show
  1. package/dist/shell/index.js +803 -88
  2. package/package.json +3 -1
@@ -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.2")
18932
- return "3.10.2";
18931
+ if ("3.10.4")
18932
+ return "3.10.4";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -80505,7 +80505,7 @@ var init_zod = __esm(() => {
80505
80505
  });
80506
80506
 
80507
80507
  // packages/workflow/src/schema.ts
80508
- var CURRENT_WORKFLOW_VERSION = 1, MarkerSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
80508
+ var CURRENT_WORKFLOW_VERSION = 2, MarkerSchema, SET_INDICATOR_KEYS, GetIndicatorSchema, SetIndicatorSchema, IndicatorsSchema, ProjectSchema, CommandsSchema, DEFAULT_META_ONLY_FILES, BoundariesSchema, WorkflowConfigSchema;
80509
80509
  var init_schema = __esm(() => {
80510
80510
  init_zod();
80511
80511
  MarkerSchema = exports_external.discriminatedUnion("type", [
@@ -80598,6 +80598,12 @@ var init_schema = __esm(() => {
80598
80598
  WorkflowConfigSchema = exports_external.object({
80599
80599
  version: exports_external.number().int().nonnegative().default(0),
80600
80600
  project: ProjectSchema,
80601
+ repo: exports_external.object({
80602
+ remote: exports_external.string().optional(),
80603
+ host: exports_external.string().optional(),
80604
+ owner: exports_external.string().optional(),
80605
+ name: exports_external.string().optional()
80606
+ }).strict().optional(),
80601
80607
  commands: CommandsSchema,
80602
80608
  rules: exports_external.array(exports_external.string()).default([]),
80603
80609
  boundaries: BoundariesSchema,
@@ -80712,8 +80718,9 @@ var init_schema = __esm(() => {
80712
80718
  advanceMergedToDone: false
80713
80719
  }),
80714
80720
  metaPrompt: exports_external.object({
80715
- enabled: exports_external.boolean().default(true)
80716
- }).strict().default({ enabled: true }),
80721
+ enabled: exports_external.boolean().default(true),
80722
+ effort: exports_external.enum(["auto", "light", "standard", "heavy"]).default("auto")
80723
+ }).strict().default({ enabled: true, effort: "auto" }),
80717
80724
  openspec: exports_external.object({
80718
80725
  reviewPhase: exports_external.object({
80719
80726
  enabled: exports_external.boolean().default(false),
@@ -81049,7 +81056,7 @@ function modelOptionValues() {
81049
81056
  const field = findField("model");
81050
81057
  return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
81051
81058
  }
81052
- var PROMPT_BODY_FIELD_ID = "promptBody", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, LINEAR_ASSIGNEE, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
81059
+ var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_ASSIGNEE, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
81053
81060
  var init_fields = __esm(() => {
81054
81061
  PROJECT_NAME = {
81055
81062
  id: "project.name",
@@ -81061,10 +81068,17 @@ var init_fields = __esm(() => {
81061
81068
  id: "linear.team",
81062
81069
  label: "Linear team key",
81063
81070
  hint: "e.g. ENG \u2014 leave blank to match all teams",
81064
- description: "Only pick up issues from this Linear team, given by its key (e.g. ENG). Leave blank to watch every team.",
81071
+ description: "The Linear team this repository is linked to, given by its key (e.g. ENG). Ralphy only picks up issues from this team. Leave blank to watch every team.",
81065
81072
  emptyLabel: "all teams",
81066
81073
  spec: { kind: "text" }
81067
81074
  };
81075
+ REPO_LINK = {
81076
+ id: "repo.link",
81077
+ label: "Link this repository to the team?",
81078
+ description: "Record the detected git repository in WORKFLOW.md and link it to the Linear team above. Confirm to adopt the detected repo; decline to leave it out.",
81079
+ spec: yes(),
81080
+ when: (answers) => typeof answers["repo.name"] === "string" && answers["repo.name"] !== ""
81081
+ };
81068
81082
  LINEAR_ASSIGNEE = {
81069
81083
  id: "linear.assignee",
81070
81084
  label: "Linear assignee",
@@ -81073,7 +81087,7 @@ var init_fields = __esm(() => {
81073
81087
  emptyLabel: "unassigned",
81074
81088
  spec: { kind: "text" }
81075
81089
  };
81076
- QUICK_FIELDS = [PROJECT_NAME, LINEAR_TEAM, LINEAR_ASSIGNEE];
81090
+ QUICK_FIELDS = [PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_ASSIGNEE];
81077
81091
  CUSTOMIZED_FIELDS = [
81078
81092
  PROJECT_NAME,
81079
81093
  {
@@ -81321,6 +81335,7 @@ var init_fields = __esm(() => {
81321
81335
  spec: { kind: "list", placeholder: "dist/**" }
81322
81336
  },
81323
81337
  LINEAR_TEAM,
81338
+ REPO_LINK,
81324
81339
  LINEAR_ASSIGNEE,
81325
81340
  {
81326
81341
  id: "linear.postComments",
@@ -81464,6 +81479,21 @@ var init_fields = __esm(() => {
81464
81479
  description: "Add Ralphy's task-level 'meta-prompt' layer (extra framing instructions) to each phase. Leave on unless you want raw prompts.",
81465
81480
  spec: yes()
81466
81481
  },
81482
+ {
81483
+ id: "metaPrompt.effort",
81484
+ label: "Per-ticket effort tier",
81485
+ description: "How much effort the meta-prompt nudges the agent toward per ticket. 'auto' detects it from the ticket; 'light'/'standard'/'heavy' pin every ticket to that tier.",
81486
+ spec: {
81487
+ kind: "select",
81488
+ options: [
81489
+ { label: "auto", value: "auto" },
81490
+ { label: "light", value: "light" },
81491
+ { label: "standard", value: "standard" },
81492
+ { label: "heavy", value: "heavy" }
81493
+ ]
81494
+ },
81495
+ when: isOn("metaPrompt.enabled")
81496
+ },
81467
81497
  {
81468
81498
  id: "openspec.reviewPhase.enabled",
81469
81499
  label: "Enable the OpenSpec review phase?",
@@ -81869,6 +81899,51 @@ var init_workflow = __esm(() => {
81869
81899
  import_yaml2 = __toESM(require_dist(), 1);
81870
81900
  });
81871
81901
 
81902
+ // packages/core/src/repo/index.ts
81903
+ function parseRepoIdentity(remoteUrl) {
81904
+ const remote = remoteUrl.trim();
81905
+ if (!remote)
81906
+ return null;
81907
+ const match = remote.includes("://") ? URL_RE.exec(remote) : SCP_RE.exec(remote);
81908
+ if (!match)
81909
+ return null;
81910
+ const host = match[1];
81911
+ let path = match[2];
81912
+ if (!host || !path)
81913
+ return null;
81914
+ path = path.replace(/\/+$/, "").replace(/\.git$/i, "").replace(/\/+$/, "");
81915
+ const segments = path.split("/").filter((segment) => segment.length > 0);
81916
+ if (segments.length < 2)
81917
+ return null;
81918
+ const name = segments[segments.length - 1];
81919
+ const owner = segments.slice(0, -1).join("/");
81920
+ if (!owner || !name)
81921
+ return null;
81922
+ return { remote, host, owner, name };
81923
+ }
81924
+ async function detectRepoIdentity(cwd2) {
81925
+ try {
81926
+ const proc = Bun.spawn({
81927
+ cmd: ["git", "remote", "get-url", "origin"],
81928
+ ...cwd2 ? { cwd: cwd2 } : {},
81929
+ stdout: "pipe",
81930
+ stderr: "ignore",
81931
+ timeout: GIT_DETECT_TIMEOUT_MS
81932
+ });
81933
+ const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
81934
+ if (exitCode !== 0)
81935
+ return null;
81936
+ return parseRepoIdentity(stdout.trim());
81937
+ } catch {
81938
+ return null;
81939
+ }
81940
+ }
81941
+ var GIT_DETECT_TIMEOUT_MS = 5000, SCP_RE, URL_RE;
81942
+ var init_repo = __esm(() => {
81943
+ SCP_RE = /^(?:[^@/]+@)?([^/:]+):(.+)$/;
81944
+ URL_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/(.+)$/;
81945
+ });
81946
+
81872
81947
  // node_modules/.bun/react@18.3.1/node_modules/react/cjs/react-jsx-dev-runtime.development.js
81873
81948
  var require_react_jsx_dev_runtime_development = __commonJS((exports) => {
81874
81949
  var React10 = __toESM(require_react());
@@ -82787,6 +82862,12 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
82787
82862
  values2["linear.indicators"] = map3;
82788
82863
  }
82789
82864
  }
82865
+ const linkRepo = values2[REPO_LINK_FIELD_ID] === true;
82866
+ delete values2[REPO_LINK_FIELD_ID];
82867
+ if (!linkRepo) {
82868
+ for (const id of REPO_ANSWER_IDS)
82869
+ delete values2[id];
82870
+ }
82790
82871
  let bodyOverride;
82791
82872
  if (PROMPT_BODY_FIELD_ID in values2) {
82792
82873
  const body = values2[PROMPT_BODY_FIELD_ID];
@@ -82868,7 +82949,8 @@ function SetupWizard({
82868
82949
  initialValues,
82869
82950
  buildMarkdown,
82870
82951
  onlyFields,
82871
- initialBody
82952
+ initialBody,
82953
+ detectedRepo
82872
82954
  }) {
82873
82955
  const { exit } = use_app_default();
82874
82956
  const startValues = initialValues ?? {};
@@ -82944,7 +83026,15 @@ function SetupWizard({
82944
83026
  setIndex(target);
82945
83027
  initEditing(fieldsFor(mode, source)[target], source);
82946
83028
  };
82947
- const valuesToWrite = (source) => onlyFields ? Object.fromEntries(Object.entries(source).filter(([id]) => onlyFields.includes(id))) : source;
83029
+ const valuesToWrite = (source) => {
83030
+ if (!onlyFields)
83031
+ return source;
83032
+ const allowed = new Set(onlyFields);
83033
+ if (allowed.has(REPO_LINK_FIELD_ID))
83034
+ for (const id of REPO_ANSWER_IDS)
83035
+ allowed.add(id);
83036
+ return Object.fromEntries(Object.entries(source).filter(([id]) => allowed.has(id)));
83037
+ };
82948
83038
  const advance = (source) => {
82949
83039
  const nextFields = fieldsFor(mode, source);
82950
83040
  if (index >= nextFields.length - 1) {
@@ -83152,6 +83242,22 @@ ${draft.slice(at2)}`, at2 + 1);
83152
83242
  marginTop: 1,
83153
83243
  flexDirection: "column",
83154
83244
  children: [
83245
+ field.id === REPO_LINK_FIELD_ID && detectedRepo ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
83246
+ children: [
83247
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
83248
+ dimColor: true,
83249
+ children: "Detected repo: "
83250
+ }, undefined, false, undefined, this),
83251
+ /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
83252
+ color: "cyan",
83253
+ children: [
83254
+ detectedRepo.owner,
83255
+ "/",
83256
+ detectedRepo.name
83257
+ ]
83258
+ }, undefined, true, undefined, this)
83259
+ ]
83260
+ }, undefined, true, undefined, this) : null,
83155
83261
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
83156
83262
  children: [
83157
83263
  /* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
@@ -83724,13 +83830,14 @@ function IndicatorBuilder({
83724
83830
  ]
83725
83831
  }, undefined, true, undefined, this);
83726
83832
  }
83727
- var import_react22, jsx_dev_runtime, MODE_OPTIONS, INDICATOR_OPTIONS, CONFIRM_OPTIONS, EDIT_EXIT_OPTIONS, RECREATE_EXIT_OPTIONS, MIGRATE_OPTIONS, CORE_STATES, CONFIRMATION_STATES, ALL_TYPES;
83833
+ var import_react22, jsx_dev_runtime, REPO_ANSWER_IDS, MODE_OPTIONS, INDICATOR_OPTIONS, CONFIRM_OPTIONS, EDIT_EXIT_OPTIONS, RECREATE_EXIT_OPTIONS, MIGRATE_OPTIONS, CORE_STATES, CONFIRMATION_STATES, ALL_TYPES;
83728
83834
  var init_SetupWizard = __esm(async () => {
83729
83835
  init_wizard();
83730
83836
  init_fields();
83731
83837
  await init_build2();
83732
83838
  import_react22 = __toESM(require_react(), 1);
83733
83839
  jsx_dev_runtime = __toESM(require_jsx_dev_runtime(), 1);
83840
+ REPO_ANSWER_IDS = ["repo.remote", "repo.host", "repo.owner", "repo.name"];
83734
83841
  MODE_OPTIONS = [
83735
83842
  { label: "Quick \u2014 sensible defaults, only a few questions", value: "quick" },
83736
83843
  { label: "Permissive \u2014 defaults + auto-PR / auto-merge / CI auto-fix", value: "permissive" },
@@ -83856,6 +83963,11 @@ var init_migrations = __esm(() => {
83856
83963
  "openspec.reviewPhase.reviewerModel",
83857
83964
  "openspec.reviewPhase.reviewerContextStrategy"
83858
83965
  ]
83966
+ },
83967
+ {
83968
+ version: 2,
83969
+ description: "Ralphy now detects the current git repo and records it in WORKFLOW.md, " + "linking it to your Linear team. Confirm the detected repo to adopt it.",
83970
+ fields: ["repo.link"]
83859
83971
  }
83860
83972
  ];
83861
83973
  LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max2, migration) => Math.max(max2, migration.version), 0);
@@ -83868,6 +83980,18 @@ __export(exports_src, {
83868
83980
  maybeRunSetupWizard: () => maybeRunSetupWizard,
83869
83981
  main: () => main
83870
83982
  });
83983
+ function withDetectedRepo(initial2, repo) {
83984
+ if (!repo)
83985
+ return initial2;
83986
+ const values2 = { ...initial2 };
83987
+ values2["repo.remote"] = repo.remote;
83988
+ values2["repo.host"] = repo.host;
83989
+ values2["repo.owner"] = repo.owner;
83990
+ values2["repo.name"] = repo.name;
83991
+ if (!values2["project.name"])
83992
+ values2["project.name"] = repo.name;
83993
+ return values2;
83994
+ }
83871
83995
  function clearScreen2() {
83872
83996
  if (process.stdout.isTTY)
83873
83997
  process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
@@ -83876,6 +84000,7 @@ async function runSetupWizard(projectRoot, options = {}) {
83876
84000
  let markdown = null;
83877
84001
  const buildMarkdown = options.existing ? (answers, bodyOverride) => applyAnswersToWorkflow(options.existing, answers, bodyOverride) : undefined;
83878
84002
  const initialBody = workflowBody(options.existing ?? DEFAULT_WORKFLOW_MD);
84003
+ const initialValues = withDetectedRepo(options.initialValues, options.detectedRepo);
83879
84004
  clearScreen2();
83880
84005
  const { waitUntilExit } = render_default(import_react23.createElement(SetupWizard, {
83881
84006
  onComplete: (md) => {
@@ -83886,8 +84011,9 @@ async function runSetupWizard(projectRoot, options = {}) {
83886
84011
  },
83887
84012
  initialBody,
83888
84013
  ...options.initialMode ? { initialMode: options.initialMode } : {},
83889
- ...options.initialValues ? { initialValues: options.initialValues } : {},
84014
+ ...initialValues ? { initialValues } : {},
83890
84015
  ...options.onlyFields ? { onlyFields: options.onlyFields } : {},
84016
+ ...options.detectedRepo ? { detectedRepo: { owner: options.detectedRepo.owner, name: options.detectedRepo.name } } : {},
83891
84017
  ...buildMarkdown ? { buildMarkdown } : {}
83892
84018
  }));
83893
84019
  await waitUntilExit();
@@ -83971,10 +84097,12 @@ async function promptMigrate(fromVersion) {
83971
84097
  }
83972
84098
  async function editExisting(projectRoot, path, config2, onlyFields) {
83973
84099
  const existing = await Bun.file(path).text();
84100
+ const detectedRepo = await detectRepoIdentity(projectRoot);
83974
84101
  const wrote = await runSetupWizard(projectRoot, {
83975
84102
  existing,
83976
84103
  initialMode: "customized",
83977
84104
  initialValues: initialValuesFromConfig(config2),
84105
+ ...detectedRepo ? { detectedRepo } : {},
83978
84106
  ...onlyFields ? { onlyFields } : {}
83979
84107
  });
83980
84108
  process.stdout.write(wrote ? `
@@ -84010,7 +84138,8 @@ async function main(argv) {
84010
84138
  `);
84011
84139
  return 0;
84012
84140
  }
84013
- const wrote2 = await runSetupWizard(projectRoot);
84141
+ const detectedRepo2 = await detectRepoIdentity(projectRoot);
84142
+ const wrote2 = await runSetupWizard(projectRoot, detectedRepo2 ? { detectedRepo: detectedRepo2 } : {});
84014
84143
  process.stdout.write(wrote2 ? `
84015
84144
  \u2713 Recreated ${path}
84016
84145
  ` : `
@@ -84043,7 +84172,8 @@ Setup cancelled \u2014 no file written.
84043
84172
  `);
84044
84173
  return 0;
84045
84174
  }
84046
- const wrote = await runSetupWizard(projectRoot);
84175
+ const detectedRepo = await detectRepoIdentity(projectRoot);
84176
+ const wrote = await runSetupWizard(projectRoot, detectedRepo ? { detectedRepo } : {});
84047
84177
  process.stdout.write(wrote ? `
84048
84178
  \u2713 Created ${path}
84049
84179
  ` : `
@@ -84056,6 +84186,7 @@ var init_src4 = __esm(async () => {
84056
84186
  init_paths();
84057
84187
  init_workflow();
84058
84188
  init_wizard();
84189
+ init_repo();
84059
84190
  init_migrations();
84060
84191
  await __promiseAll([
84061
84192
  init_build2(),
@@ -99057,12 +99188,20 @@ var init_loop_machine = __esm(() => {
99057
99188
  });
99058
99189
  });
99059
99190
 
99191
+ // packages/core/src/machines/mcp-registry.ts
99192
+ var init_mcp_registry = __esm(() => {
99193
+ init_example_machine();
99194
+ init_flow_machine();
99195
+ init_loop_machine();
99196
+ });
99197
+
99060
99198
  // packages/core/src/machines/index.ts
99061
99199
  var init_machines = __esm(() => {
99062
99200
  init_example_machine();
99063
99201
  init_flow_machine();
99064
99202
  init_flow_actor_store();
99065
99203
  init_loop_machine();
99204
+ init_mcp_registry();
99066
99205
  });
99067
99206
 
99068
99207
  // packages/core/src/tasks-md.ts
@@ -99156,10 +99295,112 @@ var init_tasks_md = __esm(() => {
99156
99295
  ];
99157
99296
  });
99158
99297
 
99298
+ // packages/core/src/prompt/effort.ts
99299
+ function countUnchecked2(tasksContent) {
99300
+ const matches2 = tasksContent.match(/^\s*[-*]\s+\[ \]/gm);
99301
+ return matches2 ? matches2.length : 0;
99302
+ }
99303
+ function clamp2(value, min2, max2) {
99304
+ return Math.max(min2, Math.min(max2, value));
99305
+ }
99306
+ function detectEffort(state, options = {}) {
99307
+ if (options.override)
99308
+ return options.override;
99309
+ const prompt = (state.prompt ?? "").toLowerCase();
99310
+ let keywordScore = 0;
99311
+ for (const kw of HEAVY_KEYWORDS) {
99312
+ if (prompt.includes(kw))
99313
+ keywordScore += HEAVY_WEIGHT;
99314
+ }
99315
+ for (const kw of LIGHT_KEYWORDS) {
99316
+ if (prompt.includes(kw))
99317
+ keywordScore += LIGHT_WEIGHT;
99318
+ }
99319
+ keywordScore = clamp2(keywordScore, -KEYWORD_HIT_CAP, KEYWORD_HIT_CAP);
99320
+ let score = keywordScore;
99321
+ const hasHeavyKeyword = keywordScore > 0;
99322
+ if (prompt.length > 0 && prompt.length < SHORT_PROMPT_CHARS && !hasHeavyKeyword) {
99323
+ score -= 1;
99324
+ } else if (prompt.length > LONG_PROMPT_CHARS) {
99325
+ score += 1;
99326
+ }
99327
+ if (options.tasksContent) {
99328
+ const unchecked = countUnchecked2(options.tasksContent);
99329
+ if (unchecked > 0 && unchecked <= FEW_TASKS) {
99330
+ score -= 2;
99331
+ } else if (unchecked >= MANY_TASKS) {
99332
+ score += 2;
99333
+ }
99334
+ }
99335
+ if (score <= LIGHT_THRESHOLD)
99336
+ return "light";
99337
+ if (score >= HEAVY_THRESHOLD)
99338
+ return "heavy";
99339
+ return "standard";
99340
+ }
99341
+ var HEAVY_KEYWORDS, LIGHT_KEYWORDS, HEAVY_WEIGHT = 2, LIGHT_WEIGHT = -2, KEYWORD_HIT_CAP = 4, SHORT_PROMPT_CHARS = 120, LONG_PROMPT_CHARS = 600, FEW_TASKS = 2, MANY_TASKS = 8, LIGHT_THRESHOLD = -2, HEAVY_THRESHOLD = 2, EFFORT_GUIDANCE;
99342
+ var init_effort = __esm(() => {
99343
+ HEAVY_KEYWORDS = [
99344
+ "migrate",
99345
+ "refactor",
99346
+ "redesign",
99347
+ "re-architect",
99348
+ "architecture",
99349
+ "rewrite",
99350
+ "overhaul",
99351
+ "breaking change",
99352
+ "investigate",
99353
+ "spike",
99354
+ "cross-cutting",
99355
+ "end-to-end"
99356
+ ];
99357
+ LIGHT_KEYWORDS = [
99358
+ "typo",
99359
+ "rename",
99360
+ "bump",
99361
+ "tweak",
99362
+ "wording",
99363
+ "copy",
99364
+ "comment",
99365
+ "lint",
99366
+ "docs",
99367
+ "one-liner",
99368
+ "revert",
99369
+ "whitespace"
99370
+ ];
99371
+ EFFORT_GUIDANCE = {
99372
+ light: [
99373
+ "This ticket looks **light**. Make the smallest correct change.",
99374
+ "- Skip research/design ceremony \u2014 go straight to the fix",
99375
+ "- Avoid speculative abstraction; do not expand scope",
99376
+ "- Aim to finish in as few iterations as possible"
99377
+ ].join(`
99378
+ `),
99379
+ standard: [
99380
+ "This ticket looks **standard**. Balance thoroughness with momentum.",
99381
+ "- Do enough investigation to be confident, but don't over-engineer",
99382
+ "- Keep changes focused on the stated scope",
99383
+ "- Verify with the project's lint and test gates before finishing"
99384
+ ].join(`
99385
+ `),
99386
+ heavy: [
99387
+ "This ticket looks **heavy**. Invest up front before changing code.",
99388
+ "- Research the affected areas and write a real design",
99389
+ "- Break the work into small, independently-verifiable tasks",
99390
+ "- Watch for cross-cutting impact and regressions as you go"
99391
+ ].join(`
99392
+ `)
99393
+ };
99394
+ });
99395
+
99159
99396
  // packages/core/src/prompt/meta-prompt.ts
99160
99397
  function buildMetaPrompt(state, phase, options = {}) {
99161
99398
  if (options.enabled === false)
99162
99399
  return "";
99400
+ const effort = detectEffort(state, {
99401
+ ...options.effort !== undefined ? { override: options.effort } : {},
99402
+ ...options.tasksContent !== undefined ? { tasksContent: options.tasksContent } : {}
99403
+ });
99163
99404
  let out = `---
99164
99405
 
99165
99406
  ## Task Context
@@ -99170,6 +99411,8 @@ function buildMetaPrompt(state, phase, options = {}) {
99170
99411
  out += `**Engine/Model:** ${state.engine} / ${state.model}
99171
99412
  `;
99172
99413
  out += `**Phase:** ${phase}
99414
+ `;
99415
+ out += `**Effort:** ${effort}
99173
99416
  `;
99174
99417
  out += `**Iteration:** ${state.iteration + 1}`;
99175
99418
  if (options.maxIterations && options.maxIterations > 0) {
@@ -99193,6 +99436,12 @@ function buildMetaPrompt(state, phase, options = {}) {
99193
99436
  `;
99194
99437
  out += PHASE_GUIDANCE[phase] + `
99195
99438
 
99439
+ `;
99440
+ out += `### Effort Guidance
99441
+
99442
+ `;
99443
+ out += EFFORT_GUIDANCE[effort] + `
99444
+
99196
99445
  `;
99197
99446
  const flags = [];
99198
99447
  if (options.useWorktree) {
@@ -99225,6 +99474,7 @@ function buildMetaPrompt(state, phase, options = {}) {
99225
99474
  }
99226
99475
  var PHASE_GUIDANCE;
99227
99476
  var init_meta_prompt = __esm(() => {
99477
+ init_effort();
99228
99478
  PHASE_GUIDANCE = {
99229
99479
  research: [
99230
99480
  "You are in the **research** phase. Your goal is to understand, not to implement.",
@@ -99698,6 +99948,7 @@ var init_loop = __esm(() => {
99698
99948
  init_tasks_md();
99699
99949
  init_phase();
99700
99950
  init_meta_prompt();
99951
+ init_effort();
99701
99952
  init_tasks_md();
99702
99953
  });
99703
99954
 
@@ -99978,7 +100229,10 @@ function useLoop(opts) {
99978
100229
  design: designContent,
99979
100230
  tasks: tasksContent
99980
100231
  });
99981
- const prompt = buildPhasePrompt(routedPhase, currentState, tasksDir, opts.reviewPhase, opts.metaPrompt);
100232
+ const prompt = buildPhasePrompt(routedPhase, currentState, tasksDir, opts.reviewPhase, {
100233
+ ...opts.metaPrompt,
100234
+ ...tasksContent !== null ? { tasksContent } : {}
100235
+ });
99982
100236
  const iterStart = new Date().toISOString();
99983
100237
  try {
99984
100238
  const controller = new AbortController;
@@ -101164,7 +101418,8 @@ async function parseAgentArgs(argv) {
101164
101418
  debug: false,
101165
101419
  noTmux: false,
101166
101420
  checks: false,
101167
- review: false
101421
+ review: false,
101422
+ ticketTokens: []
101168
101423
  };
101169
101424
  const state = emptyParseState();
101170
101425
  let expectLinearTeam = false;
@@ -101174,6 +101429,7 @@ async function parseAgentArgs(argv) {
101174
101429
  let expectMaxTickets = false;
101175
101430
  let expectIndicator = false;
101176
101431
  let expectJsonLogFile = false;
101432
+ let expectTicket = false;
101177
101433
  for (const arg of argv) {
101178
101434
  if (expectLinearTeam) {
101179
101435
  result2.linearTeam = arg;
@@ -101211,6 +101467,14 @@ async function parseAgentArgs(argv) {
101211
101467
  expectJsonLogFile = false;
101212
101468
  continue;
101213
101469
  }
101470
+ if (expectTicket) {
101471
+ for (const token of arg.split(",").map((t) => t.trim())) {
101472
+ if (token)
101473
+ result2.ticketTokens.push(token);
101474
+ }
101475
+ expectTicket = false;
101476
+ continue;
101477
+ }
101214
101478
  if (parseCommonArg(arg, result2, state))
101215
101479
  continue;
101216
101480
  switch (arg) {
@@ -101229,6 +101493,9 @@ async function parseAgentArgs(argv) {
101229
101493
  case "--max-tickets":
101230
101494
  expectMaxTickets = true;
101231
101495
  break;
101496
+ case "--ticket":
101497
+ expectTicket = true;
101498
+ break;
101232
101499
  case "--worktree":
101233
101500
  result2.worktree = true;
101234
101501
  break;
@@ -101353,6 +101620,7 @@ var init_cli2 = __esm(() => {
101353
101620
  " --stack-prs Base the PR on a blocker issue's open-PR head branch when present (needs --create-pr)",
101354
101621
  " --code-review Watch open tracked PRs for unresolved review comments",
101355
101622
  " --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
101623
+ " --ticket <id> Restrict issue discovery to specific ticket(s); repeatable or comma-separated (e.g. RLF-208 or 208)",
101356
101624
  " --no-tmux Disable tmux session management; run agent in the foreground directly",
101357
101625
  " --no-pr-tracker Disable RLF-173 pr-tracker bail / recovery counter for this run",
101358
101626
  " --json-output Emit JSONL to stdout instead of the Ink dashboard (for scripting/CI)",
@@ -101374,6 +101642,11 @@ var init_cli2 = __esm(() => {
101374
101642
  });
101375
101643
 
101376
101644
  // apps/agent/src/agent/config.ts
101645
+ var exports_config = {};
101646
+ __export(exports_config, {
101647
+ loadRalphyConfig: () => loadRalphyConfig,
101648
+ ensureRalphyConfig: () => ensureRalphyConfig
101649
+ });
101377
101650
  async function loadRalphyConfig(projectRoot) {
101378
101651
  const { config: config2 } = await loadWorkflow(projectRoot);
101379
101652
  return config2;
@@ -101734,10 +102007,107 @@ function findBoundaryViolations(changedFiles, patterns) {
101734
102007
  // apps/agent/src/shared/utils/ralph-comment.ts
101735
102008
  function isRalphComment(body) {
101736
102009
  const trimmed = body.trimStart();
101737
- return /^(\uD83E\uDD16|\uD83D\uDD04|\u2705|\u2717|\u26A0|\uD83D\uDD01|\uD83D\uDCCB)\s*Ralphy?\b/.test(trimmed);
102010
+ return /^(\uD83E\uDD16|\uD83D\uDD04|\u2705|\u2717|\u274C|\u26A0|\uD83D\uDD01|\uD83D\uDCCB|\u23F0)\s*Ralphy?\b/.test(trimmed);
101738
102011
  }
101739
102012
 
101740
102013
  // apps/agent/src/shared/capabilities/linear-client.ts
102014
+ var exports_linear_client = {};
102015
+ __export(exports_linear_client, {
102016
+ upsertRalphyAttachment: () => upsertRalphyAttachment,
102017
+ uploadFileToLinear: () => uploadFileToLinear,
102018
+ updateIssueState: () => updateIssueState,
102019
+ updateIssueDescription: () => updateIssueDescription,
102020
+ updateIssueComment: () => updateIssueComment,
102021
+ updateAttachmentSubtitle: () => updateAttachmentSubtitle,
102022
+ setIssueProject: () => setIssueProject,
102023
+ resolveTicketNumbers: () => resolveTicketNumbers,
102024
+ removeLabelFromIssue: () => removeLabelFromIssue,
102025
+ parseTicketIdentifier: () => parseTicketIdentifier,
102026
+ linearRequestInternals: () => linearRequestInternals,
102027
+ issueMatchesGetIndicator: () => issueMatchesGetIndicator,
102028
+ isRateLimitedError: () => isRateLimitedError,
102029
+ formatTicketError: () => formatTicketError,
102030
+ formatLinearError: () => formatLinearError,
102031
+ findOpenIssueByLabel: () => findOpenIssueByLabel,
102032
+ findIssueAttachmentByTitle: () => findIssueAttachmentByTitle,
102033
+ fetchWorkflowStates: () => fetchWorkflowStates,
102034
+ fetchTeamIdByKey: () => fetchTeamIdByKey,
102035
+ fetchProjectIdByName: () => fetchProjectIdByName,
102036
+ fetchOpenIssues: () => fetchOpenIssues,
102037
+ fetchMentionScanIssues: () => fetchMentionScanIssues,
102038
+ fetchIssueLabels: () => fetchIssueLabels,
102039
+ fetchIssueComments: () => fetchIssueComments,
102040
+ fetchIssueAttachments: () => fetchIssueAttachments,
102041
+ fetchAttachmentsForIssues: () => fetchAttachmentsForIssues,
102042
+ deleteIssueComment: () => deleteIssueComment,
102043
+ deleteAttachment: () => deleteAttachment,
102044
+ createRalphyAttachment: () => createRalphyAttachment,
102045
+ createIssueLabel: () => createIssueLabel,
102046
+ createIssueComment: () => createIssueComment,
102047
+ createIssue: () => createIssue,
102048
+ createAttachmentForUrl: () => createAttachmentForUrl,
102049
+ clauseFromMarkers: () => clauseFromMarkers,
102050
+ buildIssueFilter: () => buildIssueFilter,
102051
+ baseBranchFromLabels: () => baseBranchFromLabels,
102052
+ addReactionToComment: () => addReactionToComment,
102053
+ addLabelToIssue: () => addLabelToIssue,
102054
+ addIssueComment: () => addIssueComment,
102055
+ RALPHY_ATTACHMENT_TITLE: () => RALPHY_ATTACHMENT_TITLE
102056
+ });
102057
+ function parseTicketIdentifier(raw) {
102058
+ const trimmed = raw.trim();
102059
+ if (!trimmed) {
102060
+ throw new Error("--ticket value cannot be empty");
102061
+ }
102062
+ const bare = TICKET_BARE_NUMBER_RE.exec(trimmed);
102063
+ if (bare) {
102064
+ return { teamKey: null, number: Number(bare[1]) };
102065
+ }
102066
+ const match = TICKET_IDENTIFIER_RE.exec(trimmed);
102067
+ if (!match) {
102068
+ const err = new Error("--ticket value is not a Linear ticket (expected e.g. RLF-208 or 208)");
102069
+ err.value = raw;
102070
+ throw err;
102071
+ }
102072
+ return { teamKey: match[1].toUpperCase(), number: Number(match[2]) };
102073
+ }
102074
+ function resolveTicketNumbers(tokens, team) {
102075
+ const teamKey = team?.trim() ? team.trim().toUpperCase() : null;
102076
+ const seen = new Set;
102077
+ const out = [];
102078
+ for (const token of tokens) {
102079
+ const { teamKey: parsedTeam, number: number4 } = parseTicketIdentifier(token);
102080
+ if (parsedTeam !== null) {
102081
+ if (teamKey !== null && parsedTeam !== teamKey) {
102082
+ const err = new Error("--ticket identifier is not in the configured team");
102083
+ err.ticket = token;
102084
+ err.team = team;
102085
+ throw err;
102086
+ }
102087
+ } else if (teamKey === null) {
102088
+ const err = new Error("--ticket bare number needs a configured team; pass --linear-team or set linear.team in config");
102089
+ err.ticket = token;
102090
+ throw err;
102091
+ }
102092
+ if (!seen.has(number4)) {
102093
+ seen.add(number4);
102094
+ out.push(number4);
102095
+ }
102096
+ }
102097
+ return out;
102098
+ }
102099
+ function formatTicketError(err) {
102100
+ if (!(err instanceof Error))
102101
+ return String(err);
102102
+ const e = err;
102103
+ const detail = e.ticket ?? e.value;
102104
+ const parts = [];
102105
+ if (detail)
102106
+ parts.push(`ticket: ${detail}`);
102107
+ if (e.team)
102108
+ parts.push(`configured team: ${e.team}`);
102109
+ return parts.length > 0 ? `${e.message} (${parts.join(", ")})` : e.message;
102110
+ }
101741
102111
  function partition2(markers) {
101742
102112
  const statuses = [];
101743
102113
  const labels = [];
@@ -101770,6 +102140,9 @@ function buildIssueFilter(spec) {
101770
102140
  } else {
101771
102141
  where.assignee = { null: true };
101772
102142
  }
102143
+ if (spec.numbers && spec.numbers.length > 0) {
102144
+ where.number = { in: spec.numbers };
102145
+ }
101773
102146
  const inc = spec.include ?? [];
101774
102147
  if (inc.length > 0) {
101775
102148
  const { statuses, labels, attachmentSubtitles, projects } = partition2(inc);
@@ -101873,6 +102246,30 @@ function clauseFromMarkers(markers) {
101873
102246
  parts.project = { name: { in: projects } };
101874
102247
  return Object.keys(parts).length > 0 ? parts : null;
101875
102248
  }
102249
+ function mapNodeProject(node2) {
102250
+ if (!node2.project)
102251
+ return null;
102252
+ return {
102253
+ id: node2.project.id,
102254
+ name: node2.project.name,
102255
+ ...node2.project.priority !== undefined && node2.project.priority !== null ? { priority: node2.project.priority } : {}
102256
+ };
102257
+ }
102258
+ function mapNodeMilestone(node2) {
102259
+ const m = node2.projectMilestone;
102260
+ if (!m)
102261
+ return;
102262
+ return {
102263
+ id: m.id,
102264
+ name: m.name,
102265
+ sortOrder: m.sortOrder,
102266
+ ...m.targetDate != null ? { targetDate: m.targetDate } : {}
102267
+ };
102268
+ }
102269
+ function milestoneSpread(node2) {
102270
+ const m = mapNodeMilestone(node2);
102271
+ return m ? { milestone: m } : {};
102272
+ }
101876
102273
  async function fetchMentionScanIssues(apiKey, spec) {
101877
102274
  const branches = [];
101878
102275
  const { getTodo, getInProgress, setDone } = spec.indicators;
@@ -101901,13 +102298,17 @@ async function fetchMentionScanIssues(apiKey, spec) {
101901
102298
  else
101902
102299
  where.assignee = { id: { eq: spec.assignee } };
101903
102300
  }
102301
+ if (spec.numbers && spec.numbers.length > 0) {
102302
+ where.number = { in: spec.numbers };
102303
+ }
101904
102304
  const query = `query MentionScanIssues($filter: IssueFilter) {
101905
102305
  issues(filter: $filter, first: 50) {
101906
102306
  nodes {
101907
102307
  id identifier title description url priority createdAt
101908
102308
  state { name type }
101909
102309
  assignee { id email name }
101910
- project { id name }
102310
+ project { id name priority }
102311
+ projectMilestone { id name sortOrder targetDate }
101911
102312
  labels { nodes { name } }
101912
102313
  relations(first: 50) {
101913
102314
  nodes { type relatedIssue { id identifier state { type } } }
@@ -101930,7 +102331,8 @@ async function fetchMentionScanIssues(apiKey, spec) {
101930
102331
  url: n.url,
101931
102332
  state: n.state,
101932
102333
  assignee: n.assignee,
101933
- project: n.project ?? null,
102334
+ project: mapNodeProject(n),
102335
+ ...milestoneSpread(n),
101934
102336
  labels: n.labels.nodes.map((l) => l.name),
101935
102337
  priority: n.priority,
101936
102338
  createdAt: n.createdAt ?? "",
@@ -101951,7 +102353,8 @@ async function fetchOpenIssues(apiKey, spec, options) {
101951
102353
  id identifier title description url priority createdAt
101952
102354
  state { name type }
101953
102355
  assignee { id email name }
101954
- project { id name }
102356
+ project { id name priority }
102357
+ projectMilestone { id name sortOrder targetDate }
101955
102358
  labels { nodes { name } }
101956
102359
  relations(first: 50) {
101957
102360
  nodes {
@@ -101975,7 +102378,8 @@ async function fetchOpenIssues(apiKey, spec, options) {
101975
102378
  url: n.url,
101976
102379
  state: n.state,
101977
102380
  assignee: n.assignee,
101978
- project: n.project ?? null,
102381
+ project: mapNodeProject(n),
102382
+ ...milestoneSpread(n),
101979
102383
  labels: n.labels.nodes.map((l) => l.name),
101980
102384
  priority: n.priority,
101981
102385
  createdAt: n.createdAt ?? "",
@@ -102488,9 +102892,11 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
102488
102892
  labelId
102489
102893
  });
102490
102894
  }
102491
- var LINEAR_API = "https://api.linear.app/graphql", RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", linearRequestInternals, MAX_LINEAR_ATTEMPTS = 3, MAX_RETRY_AFTER_MS = 2000, BODY_TRUNCATE_CHARS = 512, RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
102895
+ var LINEAR_API = "https://api.linear.app/graphql", TICKET_IDENTIFIER_RE, TICKET_BARE_NUMBER_RE, RALPHY_ATTACHMENT_TITLE_FILTER = "Ralphy", linearRequestInternals, MAX_LINEAR_ATTEMPTS = 3, MAX_RETRY_AFTER_MS = 2000, BODY_TRUNCATE_CHARS = 512, RALPHY_ATTACHMENT_TITLE = "Ralphy", BRANCH_LABEL_PREFIX = "ralph:branch:";
102492
102896
  var init_linear_client = __esm(() => {
102493
102897
  init_types2();
102898
+ TICKET_IDENTIFIER_RE = /^([A-Za-z]+)-(\d+)(?:-.*)?$/;
102899
+ TICKET_BARE_NUMBER_RE = /^(\d+)$/;
102494
102900
  linearRequestInternals = {
102495
102901
  sleep: (ms) => Bun.sleep(ms)
102496
102902
  };
@@ -104113,17 +104519,183 @@ var init_post_task = __esm(() => {
104113
104519
  repoAutoMergeCache = new Map;
104114
104520
  });
104115
104521
 
104116
- // apps/agent/src/sort/compare.ts
104117
- function chain(...comparators) {
104118
- return (a, b) => {
104119
- for (const c of comparators) {
104120
- const r = c(a, b);
104121
- if (r !== 0)
104122
- return r;
104123
- }
104522
+ // packages/core/src/ordering/hierarchical-order.ts
104523
+ function rank(priority) {
104524
+ return !priority ? Number.POSITIVE_INFINITY : priority;
104525
+ }
104526
+ function cmpMaybe(a, b, cmp) {
104527
+ if (a === undefined && b === undefined)
104124
104528
  return 0;
104125
- };
104529
+ if (a === undefined)
104530
+ return 1;
104531
+ if (b === undefined)
104532
+ return -1;
104533
+ return cmp(a, b);
104126
104534
  }
104535
+ function cmpString(a, b) {
104536
+ return a < b ? -1 : a > b ? 1 : 0;
104537
+ }
104538
+ function cmpNumber(a, b) {
104539
+ return a - b;
104540
+ }
104541
+ function cmpRank(a, b) {
104542
+ return a === b ? 0 : a < b ? -1 : 1;
104543
+ }
104544
+ function orderIssuesHierarchically(issues, opts = {}) {
104545
+ const log3 = opts.log ?? ((message) => console.warn(message));
104546
+ if (issues.length <= 1)
104547
+ return issues.slice();
104548
+ const projectBuckets = new Map;
104549
+ for (const issue2 of issues) {
104550
+ const key = issue2.project?.id ?? NO_PROJECT;
104551
+ const bucket = projectBuckets.get(key);
104552
+ if (bucket)
104553
+ bucket.push(issue2);
104554
+ else
104555
+ projectBuckets.set(key, [issue2]);
104556
+ }
104557
+ const projectKeys = [...projectBuckets.keys()].sort((a, b) => {
104558
+ if (a === NO_PROJECT)
104559
+ return b === NO_PROJECT ? 0 : 1;
104560
+ if (b === NO_PROJECT)
104561
+ return -1;
104562
+ const ba = projectBuckets.get(a);
104563
+ const bb = projectBuckets.get(b);
104564
+ const ra = rank(ba[0]?.project?.priority);
104565
+ const rb = rank(bb[0]?.project?.priority);
104566
+ if (ra !== rb)
104567
+ return cmpRank(ra, rb);
104568
+ if (a !== b)
104569
+ return cmpString(a, b);
104570
+ return cmpString(earliestCreatedAt(ba), earliestCreatedAt(bb));
104571
+ });
104572
+ const ordered = [];
104573
+ for (const projectKey of projectKeys) {
104574
+ orderProjectBucket(projectBuckets.get(projectKey), ordered, log3);
104575
+ }
104576
+ return ordered;
104577
+ }
104578
+ function earliestCreatedAt(bucket) {
104579
+ let earliest = bucket[0].createdAt;
104580
+ for (const issue2 of bucket) {
104581
+ if (issue2.createdAt < earliest)
104582
+ earliest = issue2.createdAt;
104583
+ }
104584
+ return earliest;
104585
+ }
104586
+ function orderProjectBucket(bucket, ordered, log3) {
104587
+ const milestoneBuckets = new Map;
104588
+ for (const issue2 of bucket) {
104589
+ const key = issue2.milestone?.id ?? NO_MILESTONE;
104590
+ const b = milestoneBuckets.get(key);
104591
+ if (b)
104592
+ b.push(issue2);
104593
+ else
104594
+ milestoneBuckets.set(key, [issue2]);
104595
+ }
104596
+ const milestoneOf = new Map;
104597
+ for (const [key, b] of milestoneBuckets) {
104598
+ for (const issue2 of b)
104599
+ milestoneOf.set(issue2.id, key);
104600
+ }
104601
+ const milestonePrereqs = new Map;
104602
+ for (const key of milestoneBuckets.keys())
104603
+ milestonePrereqs.set(key, new Set);
104604
+ for (const issue2 of bucket) {
104605
+ const from = milestoneOf.get(issue2.id);
104606
+ for (const blockerId of issue2.blockedByIds) {
104607
+ const blockerMilestone = milestoneOf.get(blockerId);
104608
+ if (blockerMilestone === undefined)
104609
+ continue;
104610
+ if (blockerMilestone === from)
104611
+ continue;
104612
+ milestonePrereqs.get(from).add(blockerMilestone);
104613
+ }
104614
+ }
104615
+ const milestoneMeta = new Map;
104616
+ for (const [key, b] of milestoneBuckets) {
104617
+ milestoneMeta.set(key, key === NO_MILESTONE ? undefined : b[0].milestone);
104618
+ }
104619
+ const milestoneOrder = topoOrder([...milestoneBuckets.keys()], (key) => milestonePrereqs.get(key), (a, b) => {
104620
+ if (a === NO_MILESTONE)
104621
+ return b === NO_MILESTONE ? 0 : 1;
104622
+ if (b === NO_MILESTONE)
104623
+ return -1;
104624
+ const ma = milestoneMeta.get(a);
104625
+ const mb = milestoneMeta.get(b);
104626
+ const so = cmpNumber(ma?.sortOrder ?? 0, mb?.sortOrder ?? 0);
104627
+ if (so !== 0)
104628
+ return so;
104629
+ const td = cmpMaybe(ma?.targetDate, mb?.targetDate, cmpString);
104630
+ if (td !== 0)
104631
+ return td;
104632
+ return cmpString(a, b);
104633
+ }, (key) => log3(`hierarchical-order: milestone dependency cycle involving "${key}"; breaking by selection key`));
104634
+ for (const milestoneKey of milestoneOrder) {
104635
+ orderMilestoneBucket(milestoneBuckets.get(milestoneKey), ordered, log3);
104636
+ }
104637
+ }
104638
+ function orderMilestoneBucket(bucket, ordered, log3) {
104639
+ const inBucket = new Set(bucket.map((i) => i.id));
104640
+ const byId = new Map(bucket.map((i) => [i.id, i]));
104641
+ const order = topoOrder(bucket.map((i) => i.id), (id) => {
104642
+ const prereqs = new Set;
104643
+ for (const blockerId of byId.get(id).blockedByIds) {
104644
+ if (inBucket.has(blockerId))
104645
+ prereqs.add(blockerId);
104646
+ }
104647
+ return prereqs;
104648
+ }, (a, b) => {
104649
+ const ia = byId.get(a);
104650
+ const ib = byId.get(b);
104651
+ const rp = cmpRank(rank(ia.priority), rank(ib.priority));
104652
+ if (rp !== 0)
104653
+ return rp;
104654
+ const tb = cmpNumber(ia.tiebreak ?? 0, ib.tiebreak ?? 0);
104655
+ if (tb !== 0)
104656
+ return tb;
104657
+ const cc = cmpString(ia.createdAt, ib.createdAt);
104658
+ if (cc !== 0)
104659
+ return cc;
104660
+ return cmpString(a, b);
104661
+ }, (id) => log3(`hierarchical-order: item dependency cycle involving "${id}"; breaking by selection key`));
104662
+ for (const id of order)
104663
+ ordered.push(byId.get(id));
104664
+ }
104665
+ function topoOrder(nodes, prereqsOf, select2, onCycle) {
104666
+ const remaining = new Set(nodes);
104667
+ const placed = new Set;
104668
+ const result2 = [];
104669
+ while (remaining.size > 0) {
104670
+ const eligible = [];
104671
+ for (const node2 of remaining) {
104672
+ let ready = true;
104673
+ for (const prereq of prereqsOf(node2)) {
104674
+ if (remaining.has(prereq) && !placed.has(prereq)) {
104675
+ ready = false;
104676
+ break;
104677
+ }
104678
+ }
104679
+ if (ready)
104680
+ eligible.push(node2);
104681
+ }
104682
+ let pick3;
104683
+ if (eligible.length > 0) {
104684
+ pick3 = eligible.sort(select2)[0];
104685
+ } else {
104686
+ pick3 = [...remaining].sort(select2)[0];
104687
+ onCycle(pick3);
104688
+ }
104689
+ result2.push(pick3);
104690
+ placed.add(pick3);
104691
+ remaining.delete(pick3);
104692
+ }
104693
+ return result2;
104694
+ }
104695
+ var NO_PROJECT = "\x00no-project", NO_MILESTONE = "\x00no-milestone";
104696
+
104697
+ // packages/core/src/ordering/index.ts
104698
+ var init_ordering = () => {};
104127
104699
 
104128
104700
  // apps/agent/src/queue/queue-order.ts
104129
104701
  function defaultPriorityFor(trigger) {
@@ -104140,21 +104712,70 @@ function defaultPriorityFor(trigger) {
104140
104712
  return 4;
104141
104713
  }
104142
104714
  }
104143
- function compareQueueEntries(getAutoMerge) {
104144
- const isAutoMergeBoost = (e) => e.trigger === "conflict-fix" && issueMatchesGetIndicator(e.issue, getAutoMerge);
104145
- return chain((a, b) => Number(!isAutoMergeBoost(a)) - Number(!isAutoMergeBoost(b)), (a, b) => {
104146
- const pa = a.issue.priority === 0 ? Infinity : a.issue.priority;
104147
- const pb = b.issue.priority === 0 ? Infinity : b.issue.priority;
104148
- return pa - pb;
104149
- }, (a, b) => a.priority - b.priority, (a, b) => {
104150
- const ca = a.issue.createdAt;
104151
- const cb = b.issue.createdAt;
104152
- if (ca === cb)
104153
- return 0;
104154
- return ca < cb ? -1 : 1;
104155
- });
104715
+ function isAutoMergeBoost(e, getAutoMerge) {
104716
+ return e.trigger === "conflict-fix" && issueMatchesGetIndicator(e.issue, getAutoMerge);
104717
+ }
104718
+ function linearIssueToOrderable(issue2, tiebreak) {
104719
+ return {
104720
+ id: issue2.id,
104721
+ ...issue2.project ? {
104722
+ project: {
104723
+ id: issue2.project.id,
104724
+ ...issue2.project.priority !== undefined ? { priority: issue2.project.priority } : {}
104725
+ }
104726
+ } : {},
104727
+ ...issue2.milestone ? {
104728
+ milestone: {
104729
+ id: issue2.milestone.id,
104730
+ sortOrder: issue2.milestone.sortOrder,
104731
+ ...issue2.milestone.targetDate ? { targetDate: issue2.milestone.targetDate } : {}
104732
+ }
104733
+ } : {},
104734
+ priority: issue2.priority,
104735
+ ...tiebreak !== undefined ? { tiebreak } : {},
104736
+ blockedByIds: issue2.blockedByIds,
104737
+ createdAt: issue2.createdAt
104738
+ };
104739
+ }
104740
+ function toOrderable(entry) {
104741
+ return { ...linearIssueToOrderable(entry.issue, entry.priority), entry };
104742
+ }
104743
+ function orderEntries(entries) {
104744
+ if (entries.length <= 1)
104745
+ return entries.slice();
104746
+ const repByIssue = new Map;
104747
+ for (const entry of entries) {
104748
+ const orderable = toOrderable(entry);
104749
+ const existing = repByIssue.get(orderable.id);
104750
+ if (!existing || orderable.tiebreak < existing.tiebreak) {
104751
+ repByIssue.set(orderable.id, orderable);
104752
+ }
104753
+ }
104754
+ const rankOf = new Map;
104755
+ orderIssuesHierarchically([...repByIssue.values()]).forEach((o, i) => rankOf.set(o.id, i));
104756
+ return entries.map((entry, index) => ({ entry, index })).sort((a, b) => {
104757
+ const ra = rankOf.get(a.entry.issue.id);
104758
+ const rb = rankOf.get(b.entry.issue.id);
104759
+ if (ra !== rb)
104760
+ return ra - rb;
104761
+ if (a.entry.priority !== b.entry.priority)
104762
+ return a.entry.priority - b.entry.priority;
104763
+ return a.index - b.index;
104764
+ }).map((x) => x.entry);
104765
+ }
104766
+ function orderQueueEntries(entries, getAutoMerge) {
104767
+ const boosted = [];
104768
+ const rest2 = [];
104769
+ for (const e of entries) {
104770
+ if (isAutoMergeBoost(e, getAutoMerge))
104771
+ boosted.push(e);
104772
+ else
104773
+ rest2.push(e);
104774
+ }
104775
+ return [...orderEntries(boosted), ...orderEntries(rest2)];
104156
104776
  }
104157
104777
  var init_queue_order = __esm(() => {
104778
+ init_ordering();
104158
104779
  init_linear();
104159
104780
  });
104160
104781
 
@@ -104354,7 +104975,7 @@ class AgentCoordinator {
104354
104975
  }
104355
104976
  const prStatus = await this.scanPrMergeStates();
104356
104977
  if (this.queue.length > 0) {
104357
- this.queue.sort(compareQueueEntries(this.opts.getAutoMerge));
104978
+ this.queue = orderQueueEntries(this.queue, this.opts.getAutoMerge);
104358
104979
  }
104359
104980
  this.spawnNext();
104360
104981
  await this.reportProgress();
@@ -105580,6 +106201,10 @@ async function releaseAwaitingMarker(issue2, statePath, deps) {
105580
106201
  deps.onLog(`! persist cleared awaitingMarkerAppliedAt for ${issue2.identifier}: ${err.message}`, "yellow");
105581
106202
  }
105582
106203
  }
106204
+ function confirmationUsesCommentIndicator(cfg) {
106205
+ const { getApproved, getAutoApprove, getConfirmGate } = cfg.linear.indicators;
106206
+ return [getApproved, getAutoApprove, getConfirmGate].some((g) => g?.filter.some((m) => m.type === "comment"));
106207
+ }
105583
106208
  async function processAwaitingForIssue(issue2, deps) {
105584
106209
  try {
105585
106210
  const { cfg, apiKey, indicators } = deps;
@@ -105600,10 +106225,26 @@ async function processAwaitingForIssue(issue2, deps) {
105600
106225
  const tasks2 = await readTextOrNull(join24(changeDir, "tasks.md"));
105601
106226
  const proposal = await readTextOrNull(join24(changeDir, "proposal.md"));
105602
106227
  const design = await readTextOrNull(join24(changeDir, "design.md"));
106228
+ let commentsCache = null;
106229
+ const getComments = async () => {
106230
+ if (commentsCache)
106231
+ return commentsCache;
106232
+ if (!apiKey)
106233
+ return commentsCache = [];
106234
+ try {
106235
+ const cs = await fetchIssueComments(apiKey, issue2.id);
106236
+ commentsCache = cs.map((c) => ({ id: c.id, body: c.body, createdAt: c.createdAt }));
106237
+ } catch {
106238
+ commentsCache = [];
106239
+ }
106240
+ return commentsCache;
106241
+ };
106242
+ const commentBodies = confirmationUsesCommentIndicator(cfg) ? (await getComments()).filter((c) => !isRalphComment(c.body)).map((c) => c.body) : undefined;
105603
106243
  const ticketView = {
105604
106244
  labels: issue2.labels,
105605
106245
  state: issue2.state,
105606
- project: issue2.project
106246
+ project: issue2.project,
106247
+ ...commentBodies ? { commentBodies } : {}
105607
106248
  };
105608
106249
  const { approved: approvalMatches, confirmationGated } = computeConfirmationFlags(cfg, ticketView);
105609
106250
  const { stateObj, confirmation } = await readConfirmationState(statePath);
@@ -105675,16 +106316,7 @@ async function processAwaitingForIssue(issue2, deps) {
105675
106316
  postComments: cfg.linear.postComments !== false && Boolean(apiKey)
105676
106317
  }, {
105677
106318
  approvalMatches,
105678
- fetchComments: async () => {
105679
- if (!apiKey)
105680
- return [];
105681
- try {
105682
- const cs = await fetchIssueComments(apiKey, issue2.id);
105683
- return cs.map((c) => ({ id: c.id, body: c.body, createdAt: c.createdAt }));
105684
- } catch {
105685
- return [];
105686
- }
105687
- },
106319
+ fetchComments: getComments,
105688
106320
  ...indicators.clearApproved ? { clearApproved: indicators.clearApproved } : {},
105689
106321
  applyIndicator: (ind) => deps.applyIndicator(issue2, ind),
105690
106322
  postComment: async (body) => {
@@ -105937,6 +106569,7 @@ var init_indicators = __esm(() => {
105937
106569
  // apps/agent/src/agent/wire/linear-resolvers.ts
105938
106570
  function createLinearResolvers(input) {
105939
106571
  const { apiKey, team, assignee, diag } = input;
106572
+ const ticketNumbers = input.ticketNumbers ?? [];
105940
106573
  const stateCache = new Map;
105941
106574
  const labelCache = new Map;
105942
106575
  const teamIdCache = new Map;
@@ -106048,7 +106681,13 @@ function createLinearResolvers(input) {
106048
106681
  if (include.length === 0)
106049
106682
  return [];
106050
106683
  const hasCommentMarker = include.some((m) => m.type === "comment");
106051
- const spec = { team, assignee, include, exclude: excl };
106684
+ const spec = {
106685
+ team,
106686
+ assignee,
106687
+ include,
106688
+ exclude: excl,
106689
+ ...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
106690
+ };
106052
106691
  const fetched = await fetchOpenIssues(apiKey, spec, hasCommentMarker ? { includeComments: true } : undefined);
106053
106692
  if (!hasCommentMarker)
106054
106693
  return fetched;
@@ -106067,7 +106706,7 @@ function createLinearResolvers(input) {
106067
106706
  resolveLabelIdForTeam
106068
106707
  };
106069
106708
  }
106070
- async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators) {
106709
+ async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators, ticketNumbers) {
106071
106710
  const getIndicators = [
106072
106711
  indicators.getTodo,
106073
106712
  indicators.getInProgress,
@@ -106090,7 +106729,8 @@ async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators) {
106090
106729
  team,
106091
106730
  anyAssignee: true,
106092
106731
  include,
106093
- exclude: []
106732
+ exclude: [],
106733
+ ...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
106094
106734
  });
106095
106735
  for (const issue2 of issues) {
106096
106736
  if (!seen.has(issue2.id)) {
@@ -106579,7 +107219,7 @@ async function fetchPrReviewState(prUrl, cmdRunner, projectRoot, onLog) {
106579
107219
  reviewRequests(first:5){nodes{requestedReviewer{... on User{login}}}}
106580
107220
  latestReviews(first:5){nodes{author{login} state submittedAt}}
106581
107221
  reviewThreads(first:50){nodes{
106582
- isResolved path line
107222
+ isResolved subjectType path line
106583
107223
  comments(first:20){nodes{body author{login} createdAt url}}
106584
107224
  }}
106585
107225
  }
@@ -106612,6 +107252,7 @@ async function fetchPrReviewState(prUrl, cmdRunner, projectRoot, onLog) {
106612
107252
  approved: pr2.reviewDecision === "APPROVED",
106613
107253
  threads: (pr2.reviewThreads?.nodes ?? []).map((t) => ({
106614
107254
  isResolved: t.isResolved,
107255
+ isFileLevel: t.subjectType === "FILE",
106615
107256
  ...t.path ? { path: t.path } : {},
106616
107257
  ...t.line != null ? { line: t.line } : {},
106617
107258
  comments: t.comments.nodes.map((c) => ({
@@ -106678,7 +107319,7 @@ async function scanCodeReview(issue2, prUrl, lastRalphPickup, deps) {
106678
107319
  const effectiveLastHandled = lastRalphPickup && lastHandled ? lastRalphPickup > lastHandled ? lastRalphPickup : lastHandled : lastRalphPickup ?? lastHandled;
106679
107320
  if (!effectiveLastHandled || newestReviewerActivity > effectiveLastHandled) {
106680
107321
  const body = unresolved.map((t) => {
106681
- const head3 = t.path ? `_${t.path}${t.line ? `:${t.line}` : ""}_` : "_(general)_";
107322
+ const head3 = t.path ? t.isFileLevel ? `_${t.path} (whole file)_` : `_${t.path}${t.line ? `:${t.line}` : ""}_` : "_(general)_";
106682
107323
  const lines = t.comments.map((c) => `> **${c.author ?? "reviewer"}** (${c.createdAt})
106683
107324
  >
106684
107325
  > ${c.body.trim().replace(/\n/g, `
@@ -106783,6 +107424,7 @@ function createMentionScanner(input) {
106783
107424
  onLog,
106784
107425
  diag,
106785
107426
  cwdByChange,
107427
+ ticketNumbers,
106786
107428
  stalePingedAt,
106787
107429
  lastHandledReviewActivity,
106788
107430
  resolvePrUrlForIssue
@@ -106798,6 +107440,7 @@ function createMentionScanner(input) {
106798
107440
  candidates = await fetchMentionScanIssues(apiKey, {
106799
107441
  team,
106800
107442
  assignee,
107443
+ ...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {},
106801
107444
  indicators: {
106802
107445
  ...indicators.getTodo !== undefined ? { getTodo: indicators.getTodo } : {},
106803
107446
  ...indicators.getInProgress !== undefined ? { getInProgress: indicators.getInProgress } : {},
@@ -239276,9 +239919,9 @@ class $50c7aac9316f2948$export$2e2bcd8739ae039 {
239276
239919
 
239277
239920
  class $55f71433a605c87d$export$2e2bcd8739ae039 {
239278
239921
  process(glyphs, features = {}) {
239279
- for (let chain2 of this.morx.chains) {
239280
- let flags = chain2.defaultFlags;
239281
- for (let feature of chain2.features) {
239922
+ for (let chain of this.morx.chains) {
239923
+ let flags = chain.defaultFlags;
239924
+ for (let feature of chain.features) {
239282
239925
  let f2;
239283
239926
  if (f2 = features[feature.featureType]) {
239284
239927
  if (f2[feature.featureSetting]) {
@@ -239290,7 +239933,7 @@ class $55f71433a605c87d$export$2e2bcd8739ae039 {
239290
239933
  }
239291
239934
  }
239292
239935
  }
239293
- for (let subtable of chain2.subtables)
239936
+ for (let subtable of chain.subtables)
239294
239937
  if (subtable.subFeatureFlags & flags)
239295
239938
  this.processSubtable(subtable, glyphs);
239296
239939
  }
@@ -239437,8 +240080,8 @@ class $55f71433a605c87d$export$2e2bcd8739ae039 {
239437
240080
  }
239438
240081
  getSupportedFeatures() {
239439
240082
  let features = [];
239440
- for (let chain2 of this.morx.chains)
239441
- for (let feature of chain2.features)
240083
+ for (let chain of this.morx.chains)
240084
+ for (let feature of chain.features)
239442
240085
  features.push([
239443
240086
  feature.featureType,
239444
240087
  feature.featureSetting
@@ -239452,9 +240095,9 @@ class $55f71433a605c87d$export$2e2bcd8739ae039 {
239452
240095
  }
239453
240096
  generateInputCache() {
239454
240097
  this.inputCache = {};
239455
- for (let chain2 of this.morx.chains) {
239456
- let flags = chain2.defaultFlags;
239457
- for (let subtable of chain2.subtables)
240098
+ for (let chain of this.morx.chains) {
240099
+ let flags = chain.defaultFlags;
240100
+ for (let subtable of chain.subtables)
239458
240101
  if (subtable.subFeatureFlags & flags)
239459
240102
  this.generateInputsForSubtable(subtable);
239460
240103
  }
@@ -260537,6 +261180,7 @@ function buildAgentCoordinator(input) {
260537
261180
  const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
260538
261181
  const team = args.linearTeam || cfg.linear.team;
260539
261182
  const assignee = args.linearAssignee || cfg.linear.assignee;
261183
+ const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
260540
261184
  const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
260541
261185
  const gitRunner = input.runners?.git ?? bunGitRunner;
260542
261186
  const cmdRunner = input.runners?.cmd ?? bunCmdRunner;
@@ -260568,7 +261212,19 @@ function buildAgentCoordinator(input) {
260568
261212
  }
260569
261213
  return code;
260570
261214
  });
260571
- const resolvers = createLinearResolvers({ apiKey, team, assignee, diag });
261215
+ const resolvers = createLinearResolvers({
261216
+ apiKey,
261217
+ team,
261218
+ assignee,
261219
+ diag,
261220
+ ...ticketNumbers.length > 0 ? { ticketNumbers } : {}
261221
+ });
261222
+ if (ticketNumbers.length > 0) {
261223
+ const hasGetIndicator = [indicators.getTodo, indicators.getInProgress].some((ind) => ind && ind.filter.length > 0);
261224
+ if (!hasGetIndicator) {
261225
+ diag("ticket", `! --ticket set (${ticketNumbers.join(", ")}) but no getTodo/getInProgress indicator is configured \u2014 nothing will be picked up`, "yellow");
261226
+ }
261227
+ }
260572
261228
  const prDiscovery = createPrDiscovery({
260573
261229
  apiKey,
260574
261230
  projectRoot,
@@ -260604,6 +261260,7 @@ function buildAgentCoordinator(input) {
260604
261260
  onLog,
260605
261261
  diag,
260606
261262
  cwdByChange,
261263
+ ...ticketNumbers.length > 0 ? { ticketNumbers } : {},
260607
261264
  stalePingedAt,
260608
261265
  lastHandledReviewActivity,
260609
261266
  resolvePrUrlForIssue: prDiscovery.resolvePrUrlForIssue
@@ -260700,7 +261357,7 @@ function buildAgentCoordinator(input) {
260700
261357
  fetchTodo: () => resolvers.fetchByGet(indicators.getTodo, excludeFromTodo),
260701
261358
  fetchInProgress: () => resolvers.fetchByGet(indicators.getInProgress, unionMarkers(indicators.setError)),
260702
261359
  fetchMentions,
260703
- fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, indicators),
261360
+ fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, indicators, ticketNumbers.length > 0 ? ticketNumbers : undefined),
260704
261361
  prepare: prep.prepare,
260705
261362
  prepareTaskForTrigger: prep.prepareTaskForTrigger,
260706
261363
  spawnWorker,
@@ -260786,6 +261443,7 @@ var init_wire = __esm(() => {
260786
261443
  init_indicators();
260787
261444
  init_task_bodies();
260788
261445
  init_linear_resolvers();
261446
+ init_linear_client();
260789
261447
  init_prepare();
260790
261448
  init_pr_discovery();
260791
261449
  init_mention_scan();
@@ -260801,7 +261459,7 @@ import { dirname as dirname15 } from "path";
260801
261459
  function createJsonLogFileSink(path) {
260802
261460
  if (!path)
260803
261461
  return { emit: () => {} };
260804
- let chain2 = (async () => {
261462
+ let chain = (async () => {
260805
261463
  try {
260806
261464
  await mkdir12(dirname15(path), { recursive: true });
260807
261465
  await Bun.write(path, "");
@@ -260811,7 +261469,7 @@ function createJsonLogFileSink(path) {
260811
261469
  emit(event) {
260812
261470
  const line = JSON.stringify({ ts: Date.now(), v: VERSION, ...event }) + `
260813
261471
  `;
260814
- chain2 = chain2.then(async () => {
261472
+ chain = chain.then(async () => {
260815
261473
  try {
260816
261474
  await appendFile2(path, line);
260817
261475
  } catch {}
@@ -262919,6 +263577,18 @@ var init_tmux = __esm(() => {
262919
263577
  decoder = new TextDecoder;
262920
263578
  });
262921
263579
 
263580
+ // apps/agent/src/sort/compare.ts
263581
+ function chain(...comparators) {
263582
+ return (a, b2) => {
263583
+ for (const c of comparators) {
263584
+ const r = c(a, b2);
263585
+ if (r !== 0)
263586
+ return r;
263587
+ }
263588
+ return 0;
263589
+ };
263590
+ }
263591
+
262922
263592
  // apps/agent/src/list-sort.ts
262923
263593
  function assignTier(status) {
262924
263594
  if (status === null || status.kind === "error")
@@ -262941,7 +263611,7 @@ function createdAtOf(status) {
262941
263611
  return "";
262942
263612
  }
262943
263613
  function sortRows(rows) {
262944
- const cmp = chain((a, b2) => assignTier(a.status) - assignTier(b2.status), (a, b2) => {
263614
+ const cmp = chain((a, b2) => assignTier(a.status) - assignTier(b2.status), (a, b2) => a.bucketOrder - b2.bucketOrder, (a, b2) => {
262945
263615
  const ia = a.issueCreatedAt;
262946
263616
  const ib = b2.issueCreatedAt;
262947
263617
  if (ia === ib)
@@ -262957,7 +263627,7 @@ function sortRows(rows) {
262957
263627
  if (ca === cb)
262958
263628
  return 0;
262959
263629
  return ca < cb ? -1 : 1;
262960
- }, (a, b2) => a.bucketOrder - b2.bucketOrder, (a, b2) => a.identifier.localeCompare(b2.identifier));
263630
+ }, (a, b2) => a.identifier.localeCompare(b2.identifier));
262961
263631
  return [...rows].sort(cmp);
262962
263632
  }
262963
263633
  var init_list_sort = () => {};
@@ -263006,7 +263676,8 @@ __export(exports_list, {
263006
263676
  formatReviewCell: () => formatReviewCell,
263007
263677
  formatPrStatusMarker: () => formatPrStatusMarker,
263008
263678
  formatBlockedCell: () => formatBlockedCell,
263009
- buildBuckets: () => buildBuckets
263679
+ buildBuckets: () => buildBuckets,
263680
+ backlogRankByIssueId: () => backlogRankByIssueId
263010
263681
  });
263011
263682
  import { join as join36 } from "path";
263012
263683
  function countTaskItems(content) {
@@ -263108,14 +263779,15 @@ function buildBuckets(indicators) {
263108
263779
  { label: "auto-merge", indicator: indicators.getAutoMerge, exclude: [] }
263109
263780
  ];
263110
263781
  }
263111
- async function fetchBucketIssues(apiKey, bucket, team, assignee) {
263782
+ async function fetchBucketIssues(apiKey, bucket, team, assignee, ticketNumbers) {
263112
263783
  if (!bucket.indicator || bucket.indicator.filter.length === 0)
263113
263784
  return [];
263114
263785
  const spec = {
263115
263786
  team,
263116
263787
  assignee,
263117
263788
  include: bucket.indicator.filter,
263118
- exclude: bucket.exclude
263789
+ exclude: bucket.exclude,
263790
+ ...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
263119
263791
  };
263120
263792
  return fetchOpenIssues(apiKey, spec);
263121
263793
  }
@@ -263159,13 +263831,19 @@ function formatPrStatusMarker(status, failedCheckNames) {
263159
263831
  return "ok";
263160
263832
  return parts.join(" ");
263161
263833
  }
263162
- async function fetchAndPrintLinear(apiKey, buckets, team, assignee, cwd2, runner, ignoreCiChecks = [], checks3 = false, review = false) {
263834
+ function backlogRankByIssueId(issues) {
263835
+ const ordered = orderIssuesHierarchically(issues.map((issue2) => linearIssueToOrderable(issue2)));
263836
+ const rankById = new Map;
263837
+ ordered.forEach((o, i) => rankById.set(o.id, i));
263838
+ return rankById;
263839
+ }
263840
+ async function fetchAndPrintLinear(apiKey, buckets, team, assignee, cwd2, runner, ignoreCiChecks = [], checks3 = false, review = false, ticketNumbers = []) {
263163
263841
  const bucketResults = await Promise.all(buckets.map(async (bucket) => {
263164
263842
  if (!bucket.indicator || bucket.indicator.filter.length === 0) {
263165
263843
  return { bucket, issues: [], error: null };
263166
263844
  }
263167
263845
  try {
263168
- const issues = await fetchBucketIssues(apiKey, bucket, team, assignee);
263846
+ const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, ticketNumbers);
263169
263847
  return { bucket, issues, error: null };
263170
263848
  } catch (err) {
263171
263849
  return {
@@ -263183,16 +263861,17 @@ ${bucket.label}: error fetching from Linear \u2014 ${error48}
263183
263861
  }
263184
263862
  }
263185
263863
  const seen = new Map;
263186
- let order = 0;
263864
+ const issueById = new Map;
263187
263865
  for (const { bucket, issues } of bucketResults) {
263188
263866
  for (const issue2 of issues) {
263189
263867
  if (seen.has(issue2.id))
263190
263868
  continue;
263869
+ issueById.set(issue2.id, issue2);
263191
263870
  seen.set(issue2.id, {
263192
263871
  issueId: issue2.id,
263193
263872
  identifier: issue2.identifier,
263194
263873
  status: null,
263195
- bucketOrder: order++,
263874
+ bucketOrder: 0,
263196
263875
  issueCreatedAt: issue2.createdAt,
263197
263876
  bucketLabel: bucket.label,
263198
263877
  stateName: issue2.state.name,
@@ -263203,6 +263882,9 @@ ${bucket.label}: error fetching from Linear \u2014 ${error48}
263203
263882
  }
263204
263883
  }
263205
263884
  const rows = [...seen.values()];
263885
+ const rankById = backlogRankByIssueId([...issueById.values()]);
263886
+ for (const row of rows)
263887
+ row.bucketOrder = rankById.get(row.issueId) ?? 0;
263206
263888
  try {
263207
263889
  const attachmentsByIssue = await fetchAttachmentsForIssues(apiKey, rows.map((r) => r.issueId));
263208
263890
  for (const row of rows) {
@@ -263319,6 +264001,15 @@ Linear: LINEAR_API_KEY not set \u2014 cannot fetch tickets. Configured buckets:
263319
264001
  }
263320
264002
  return;
263321
264003
  }
264004
+ let ticketNumbers = [];
264005
+ try {
264006
+ ticketNumbers = resolveTicketNumbers(input.ticketTokens ?? [], team);
264007
+ } catch (err) {
264008
+ process.stderr.write(`Error: ${formatTicketError(err)}
264009
+ `);
264010
+ process.exitCode = 1;
264011
+ return;
264012
+ }
263322
264013
  if (team)
263323
264014
  process.stdout.write(`
263324
264015
  team: ${team}
@@ -263326,13 +264017,21 @@ team: ${team}
263326
264017
  if (assignee)
263327
264018
  process.stdout.write(`assignee: ${assignee}
263328
264019
  `);
263329
- await fetchAndPrintLinear(apiKey, buckets, team, assignee, projectRoot, localCmdRunner, cfg.ignoreCiChecks, input.checks, input.review);
264020
+ if (ticketNumbers.length > 0)
264021
+ process.stdout.write(`ticket: ${ticketNumbers.join(", ")}
264022
+ `);
264023
+ await fetchAndPrintLinear(apiKey, buckets, team, assignee, projectRoot, localCmdRunner, cfg.ignoreCiChecks, input.checks, input.review, ticketNumbers);
263330
264024
  }
263331
264025
  function normalizeIdentifier(input) {
263332
- const match = input.match(/^([A-Za-z]+)-(\d+)(?:-.*)?$/);
263333
- if (!match)
264026
+ let parsed;
264027
+ try {
264028
+ parsed = parseTicketIdentifier(input);
264029
+ } catch {
263334
264030
  return null;
263335
- return `${match[1].toUpperCase()}-${match[2]}`;
264031
+ }
264032
+ if (parsed.teamKey === null)
264033
+ return null;
264034
+ return `${parsed.teamKey}-${parsed.number}`;
263336
264035
  }
263337
264036
  async function fetchIssueByIdentifier(apiKey, identifier) {
263338
264037
  const match = identifier.match(/^([A-Z]+)-(\d+)$/);
@@ -263482,6 +264181,8 @@ var init_list = __esm(() => {
263482
264181
  init_pr_status();
263483
264182
  init_pr_url();
263484
264183
  init_list_sort();
264184
+ init_ordering();
264185
+ init_queue_order();
263485
264186
  init_ci();
263486
264187
  init_linear_client();
263487
264188
  init_indicators();
@@ -263791,7 +264492,8 @@ async function main3(argv) {
263791
264492
  debug: args.debug,
263792
264493
  name: args.name,
263793
264494
  checks: args.checks,
263794
- review: args.review
264495
+ review: args.review,
264496
+ ticketTokens: args.ticketTokens
263795
264497
  });
263796
264498
  });
263797
264499
  return typeof process.exitCode === "number" ? process.exitCode : 0;
@@ -263811,6 +264513,19 @@ async function main3(argv) {
263811
264513
  `);
263812
264514
  return 0;
263813
264515
  }
264516
+ if (args.ticketTokens.length > 0) {
264517
+ const { loadRalphyConfig: loadRalphyConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
264518
+ const { resolveTicketNumbers: resolveTicketNumbers2, formatTicketError: formatTicketError2 } = await Promise.resolve().then(() => (init_linear_client(), exports_linear_client));
264519
+ const cfg = await loadRalphyConfig2(projectRoot);
264520
+ const team = args.linearTeam || cfg.linear.team;
264521
+ try {
264522
+ resolveTicketNumbers2(args.ticketTokens, team);
264523
+ } catch (err) {
264524
+ process.stderr.write(formatTicketError2(err) + `
264525
+ `);
264526
+ return 1;
264527
+ }
264528
+ }
263814
264529
  await mkdir15(statesDir, { recursive: true });
263815
264530
  await mkdir15(tasksDir, { recursive: true });
263816
264531
  await mkdir15(join39(projectRoot, ".ralph"), { recursive: true });