@neriros/ralphy 3.10.3 → 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 +772 -76
  2. package/package.json +1 -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.3")
18932
- return "3.10.3";
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(),
@@ -99164,10 +99295,112 @@ var init_tasks_md = __esm(() => {
99164
99295
  ];
99165
99296
  });
99166
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
+
99167
99396
  // packages/core/src/prompt/meta-prompt.ts
99168
99397
  function buildMetaPrompt(state, phase, options = {}) {
99169
99398
  if (options.enabled === false)
99170
99399
  return "";
99400
+ const effort = detectEffort(state, {
99401
+ ...options.effort !== undefined ? { override: options.effort } : {},
99402
+ ...options.tasksContent !== undefined ? { tasksContent: options.tasksContent } : {}
99403
+ });
99171
99404
  let out = `---
99172
99405
 
99173
99406
  ## Task Context
@@ -99178,6 +99411,8 @@ function buildMetaPrompt(state, phase, options = {}) {
99178
99411
  out += `**Engine/Model:** ${state.engine} / ${state.model}
99179
99412
  `;
99180
99413
  out += `**Phase:** ${phase}
99414
+ `;
99415
+ out += `**Effort:** ${effort}
99181
99416
  `;
99182
99417
  out += `**Iteration:** ${state.iteration + 1}`;
99183
99418
  if (options.maxIterations && options.maxIterations > 0) {
@@ -99201,6 +99436,12 @@ function buildMetaPrompt(state, phase, options = {}) {
99201
99436
  `;
99202
99437
  out += PHASE_GUIDANCE[phase] + `
99203
99438
 
99439
+ `;
99440
+ out += `### Effort Guidance
99441
+
99442
+ `;
99443
+ out += EFFORT_GUIDANCE[effort] + `
99444
+
99204
99445
  `;
99205
99446
  const flags = [];
99206
99447
  if (options.useWorktree) {
@@ -99233,6 +99474,7 @@ function buildMetaPrompt(state, phase, options = {}) {
99233
99474
  }
99234
99475
  var PHASE_GUIDANCE;
99235
99476
  var init_meta_prompt = __esm(() => {
99477
+ init_effort();
99236
99478
  PHASE_GUIDANCE = {
99237
99479
  research: [
99238
99480
  "You are in the **research** phase. Your goal is to understand, not to implement.",
@@ -99706,6 +99948,7 @@ var init_loop = __esm(() => {
99706
99948
  init_tasks_md();
99707
99949
  init_phase();
99708
99950
  init_meta_prompt();
99951
+ init_effort();
99709
99952
  init_tasks_md();
99710
99953
  });
99711
99954
 
@@ -99986,7 +100229,10 @@ function useLoop(opts) {
99986
100229
  design: designContent,
99987
100230
  tasks: tasksContent
99988
100231
  });
99989
- 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
+ });
99990
100236
  const iterStart = new Date().toISOString();
99991
100237
  try {
99992
100238
  const controller = new AbortController;
@@ -101172,7 +101418,8 @@ async function parseAgentArgs(argv) {
101172
101418
  debug: false,
101173
101419
  noTmux: false,
101174
101420
  checks: false,
101175
- review: false
101421
+ review: false,
101422
+ ticketTokens: []
101176
101423
  };
101177
101424
  const state = emptyParseState();
101178
101425
  let expectLinearTeam = false;
@@ -101182,6 +101429,7 @@ async function parseAgentArgs(argv) {
101182
101429
  let expectMaxTickets = false;
101183
101430
  let expectIndicator = false;
101184
101431
  let expectJsonLogFile = false;
101432
+ let expectTicket = false;
101185
101433
  for (const arg of argv) {
101186
101434
  if (expectLinearTeam) {
101187
101435
  result2.linearTeam = arg;
@@ -101219,6 +101467,14 @@ async function parseAgentArgs(argv) {
101219
101467
  expectJsonLogFile = false;
101220
101468
  continue;
101221
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
+ }
101222
101478
  if (parseCommonArg(arg, result2, state))
101223
101479
  continue;
101224
101480
  switch (arg) {
@@ -101237,6 +101493,9 @@ async function parseAgentArgs(argv) {
101237
101493
  case "--max-tickets":
101238
101494
  expectMaxTickets = true;
101239
101495
  break;
101496
+ case "--ticket":
101497
+ expectTicket = true;
101498
+ break;
101240
101499
  case "--worktree":
101241
101500
  result2.worktree = true;
101242
101501
  break;
@@ -101361,6 +101620,7 @@ var init_cli2 = __esm(() => {
101361
101620
  " --stack-prs Base the PR on a blocker issue's open-PR head branch when present (needs --create-pr)",
101362
101621
  " --code-review Watch open tracked PRs for unresolved review comments",
101363
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)",
101364
101624
  " --no-tmux Disable tmux session management; run agent in the foreground directly",
101365
101625
  " --no-pr-tracker Disable RLF-173 pr-tracker bail / recovery counter for this run",
101366
101626
  " --json-output Emit JSONL to stdout instead of the Ink dashboard (for scripting/CI)",
@@ -101382,6 +101642,11 @@ var init_cli2 = __esm(() => {
101382
101642
  });
101383
101643
 
101384
101644
  // apps/agent/src/agent/config.ts
101645
+ var exports_config = {};
101646
+ __export(exports_config, {
101647
+ loadRalphyConfig: () => loadRalphyConfig,
101648
+ ensureRalphyConfig: () => ensureRalphyConfig
101649
+ });
101385
101650
  async function loadRalphyConfig(projectRoot) {
101386
101651
  const { config: config2 } = await loadWorkflow(projectRoot);
101387
101652
  return config2;
@@ -101746,6 +102011,103 @@ function isRalphComment(body) {
101746
102011
  }
101747
102012
 
101748
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
+ }
101749
102111
  function partition2(markers) {
101750
102112
  const statuses = [];
101751
102113
  const labels = [];
@@ -101778,6 +102140,9 @@ function buildIssueFilter(spec) {
101778
102140
  } else {
101779
102141
  where.assignee = { null: true };
101780
102142
  }
102143
+ if (spec.numbers && spec.numbers.length > 0) {
102144
+ where.number = { in: spec.numbers };
102145
+ }
101781
102146
  const inc = spec.include ?? [];
101782
102147
  if (inc.length > 0) {
101783
102148
  const { statuses, labels, attachmentSubtitles, projects } = partition2(inc);
@@ -101881,6 +102246,30 @@ function clauseFromMarkers(markers) {
101881
102246
  parts.project = { name: { in: projects } };
101882
102247
  return Object.keys(parts).length > 0 ? parts : null;
101883
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
+ }
101884
102273
  async function fetchMentionScanIssues(apiKey, spec) {
101885
102274
  const branches = [];
101886
102275
  const { getTodo, getInProgress, setDone } = spec.indicators;
@@ -101909,13 +102298,17 @@ async function fetchMentionScanIssues(apiKey, spec) {
101909
102298
  else
101910
102299
  where.assignee = { id: { eq: spec.assignee } };
101911
102300
  }
102301
+ if (spec.numbers && spec.numbers.length > 0) {
102302
+ where.number = { in: spec.numbers };
102303
+ }
101912
102304
  const query = `query MentionScanIssues($filter: IssueFilter) {
101913
102305
  issues(filter: $filter, first: 50) {
101914
102306
  nodes {
101915
102307
  id identifier title description url priority createdAt
101916
102308
  state { name type }
101917
102309
  assignee { id email name }
101918
- project { id name }
102310
+ project { id name priority }
102311
+ projectMilestone { id name sortOrder targetDate }
101919
102312
  labels { nodes { name } }
101920
102313
  relations(first: 50) {
101921
102314
  nodes { type relatedIssue { id identifier state { type } } }
@@ -101938,7 +102331,8 @@ async function fetchMentionScanIssues(apiKey, spec) {
101938
102331
  url: n.url,
101939
102332
  state: n.state,
101940
102333
  assignee: n.assignee,
101941
- project: n.project ?? null,
102334
+ project: mapNodeProject(n),
102335
+ ...milestoneSpread(n),
101942
102336
  labels: n.labels.nodes.map((l) => l.name),
101943
102337
  priority: n.priority,
101944
102338
  createdAt: n.createdAt ?? "",
@@ -101959,7 +102353,8 @@ async function fetchOpenIssues(apiKey, spec, options) {
101959
102353
  id identifier title description url priority createdAt
101960
102354
  state { name type }
101961
102355
  assignee { id email name }
101962
- project { id name }
102356
+ project { id name priority }
102357
+ projectMilestone { id name sortOrder targetDate }
101963
102358
  labels { nodes { name } }
101964
102359
  relations(first: 50) {
101965
102360
  nodes {
@@ -101983,7 +102378,8 @@ async function fetchOpenIssues(apiKey, spec, options) {
101983
102378
  url: n.url,
101984
102379
  state: n.state,
101985
102380
  assignee: n.assignee,
101986
- project: n.project ?? null,
102381
+ project: mapNodeProject(n),
102382
+ ...milestoneSpread(n),
101987
102383
  labels: n.labels.nodes.map((l) => l.name),
101988
102384
  priority: n.priority,
101989
102385
  createdAt: n.createdAt ?? "",
@@ -102496,9 +102892,11 @@ async function removeLabelFromIssue(apiKey, issueId, labelId) {
102496
102892
  labelId
102497
102893
  });
102498
102894
  }
102499
- 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:";
102500
102896
  var init_linear_client = __esm(() => {
102501
102897
  init_types2();
102898
+ TICKET_IDENTIFIER_RE = /^([A-Za-z]+)-(\d+)(?:-.*)?$/;
102899
+ TICKET_BARE_NUMBER_RE = /^(\d+)$/;
102502
102900
  linearRequestInternals = {
102503
102901
  sleep: (ms) => Bun.sleep(ms)
102504
102902
  };
@@ -104121,17 +104519,183 @@ var init_post_task = __esm(() => {
104121
104519
  repoAutoMergeCache = new Map;
104122
104520
  });
104123
104521
 
104124
- // apps/agent/src/sort/compare.ts
104125
- function chain(...comparators) {
104126
- return (a, b) => {
104127
- for (const c of comparators) {
104128
- const r = c(a, b);
104129
- if (r !== 0)
104130
- return r;
104131
- }
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)
104132
104528
  return 0;
104133
- };
104529
+ if (a === undefined)
104530
+ return 1;
104531
+ if (b === undefined)
104532
+ return -1;
104533
+ return cmp(a, b);
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;
104134
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 = () => {};
104135
104699
 
104136
104700
  // apps/agent/src/queue/queue-order.ts
104137
104701
  function defaultPriorityFor(trigger) {
@@ -104148,21 +104712,70 @@ function defaultPriorityFor(trigger) {
104148
104712
  return 4;
104149
104713
  }
104150
104714
  }
104151
- function compareQueueEntries(getAutoMerge) {
104152
- const isAutoMergeBoost = (e) => e.trigger === "conflict-fix" && issueMatchesGetIndicator(e.issue, getAutoMerge);
104153
- return chain((a, b) => Number(!isAutoMergeBoost(a)) - Number(!isAutoMergeBoost(b)), (a, b) => {
104154
- const pa = a.issue.priority === 0 ? Infinity : a.issue.priority;
104155
- const pb = b.issue.priority === 0 ? Infinity : b.issue.priority;
104156
- return pa - pb;
104157
- }, (a, b) => a.priority - b.priority, (a, b) => {
104158
- const ca = a.issue.createdAt;
104159
- const cb = b.issue.createdAt;
104160
- if (ca === cb)
104161
- return 0;
104162
- return ca < cb ? -1 : 1;
104163
- });
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)];
104164
104776
  }
104165
104777
  var init_queue_order = __esm(() => {
104778
+ init_ordering();
104166
104779
  init_linear();
104167
104780
  });
104168
104781
 
@@ -104362,7 +104975,7 @@ class AgentCoordinator {
104362
104975
  }
104363
104976
  const prStatus = await this.scanPrMergeStates();
104364
104977
  if (this.queue.length > 0) {
104365
- this.queue.sort(compareQueueEntries(this.opts.getAutoMerge));
104978
+ this.queue = orderQueueEntries(this.queue, this.opts.getAutoMerge);
104366
104979
  }
104367
104980
  this.spawnNext();
104368
104981
  await this.reportProgress();
@@ -105956,6 +106569,7 @@ var init_indicators = __esm(() => {
105956
106569
  // apps/agent/src/agent/wire/linear-resolvers.ts
105957
106570
  function createLinearResolvers(input) {
105958
106571
  const { apiKey, team, assignee, diag } = input;
106572
+ const ticketNumbers = input.ticketNumbers ?? [];
105959
106573
  const stateCache = new Map;
105960
106574
  const labelCache = new Map;
105961
106575
  const teamIdCache = new Map;
@@ -106067,7 +106681,13 @@ function createLinearResolvers(input) {
106067
106681
  if (include.length === 0)
106068
106682
  return [];
106069
106683
  const hasCommentMarker = include.some((m) => m.type === "comment");
106070
- 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
+ };
106071
106691
  const fetched = await fetchOpenIssues(apiKey, spec, hasCommentMarker ? { includeComments: true } : undefined);
106072
106692
  if (!hasCommentMarker)
106073
106693
  return fetched;
@@ -106086,7 +106706,7 @@ function createLinearResolvers(input) {
106086
106706
  resolveLabelIdForTeam
106087
106707
  };
106088
106708
  }
106089
- async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators) {
106709
+ async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators, ticketNumbers) {
106090
106710
  const getIndicators = [
106091
106711
  indicators.getTodo,
106092
106712
  indicators.getInProgress,
@@ -106109,7 +106729,8 @@ async function fetchDoneCandidatesWith(apiKey, team, _assignee, indicators) {
106109
106729
  team,
106110
106730
  anyAssignee: true,
106111
106731
  include,
106112
- exclude: []
106732
+ exclude: [],
106733
+ ...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
106113
106734
  });
106114
106735
  for (const issue2 of issues) {
106115
106736
  if (!seen.has(issue2.id)) {
@@ -106598,7 +107219,7 @@ async function fetchPrReviewState(prUrl, cmdRunner, projectRoot, onLog) {
106598
107219
  reviewRequests(first:5){nodes{requestedReviewer{... on User{login}}}}
106599
107220
  latestReviews(first:5){nodes{author{login} state submittedAt}}
106600
107221
  reviewThreads(first:50){nodes{
106601
- isResolved path line
107222
+ isResolved subjectType path line
106602
107223
  comments(first:20){nodes{body author{login} createdAt url}}
106603
107224
  }}
106604
107225
  }
@@ -106631,6 +107252,7 @@ async function fetchPrReviewState(prUrl, cmdRunner, projectRoot, onLog) {
106631
107252
  approved: pr2.reviewDecision === "APPROVED",
106632
107253
  threads: (pr2.reviewThreads?.nodes ?? []).map((t) => ({
106633
107254
  isResolved: t.isResolved,
107255
+ isFileLevel: t.subjectType === "FILE",
106634
107256
  ...t.path ? { path: t.path } : {},
106635
107257
  ...t.line != null ? { line: t.line } : {},
106636
107258
  comments: t.comments.nodes.map((c) => ({
@@ -106697,7 +107319,7 @@ async function scanCodeReview(issue2, prUrl, lastRalphPickup, deps) {
106697
107319
  const effectiveLastHandled = lastRalphPickup && lastHandled ? lastRalphPickup > lastHandled ? lastRalphPickup : lastHandled : lastRalphPickup ?? lastHandled;
106698
107320
  if (!effectiveLastHandled || newestReviewerActivity > effectiveLastHandled) {
106699
107321
  const body = unresolved.map((t) => {
106700
- 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)_";
106701
107323
  const lines = t.comments.map((c) => `> **${c.author ?? "reviewer"}** (${c.createdAt})
106702
107324
  >
106703
107325
  > ${c.body.trim().replace(/\n/g, `
@@ -106802,6 +107424,7 @@ function createMentionScanner(input) {
106802
107424
  onLog,
106803
107425
  diag,
106804
107426
  cwdByChange,
107427
+ ticketNumbers,
106805
107428
  stalePingedAt,
106806
107429
  lastHandledReviewActivity,
106807
107430
  resolvePrUrlForIssue
@@ -106817,6 +107440,7 @@ function createMentionScanner(input) {
106817
107440
  candidates = await fetchMentionScanIssues(apiKey, {
106818
107441
  team,
106819
107442
  assignee,
107443
+ ...ticketNumbers && ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {},
106820
107444
  indicators: {
106821
107445
  ...indicators.getTodo !== undefined ? { getTodo: indicators.getTodo } : {},
106822
107446
  ...indicators.getInProgress !== undefined ? { getInProgress: indicators.getInProgress } : {},
@@ -239295,9 +239919,9 @@ class $50c7aac9316f2948$export$2e2bcd8739ae039 {
239295
239919
 
239296
239920
  class $55f71433a605c87d$export$2e2bcd8739ae039 {
239297
239921
  process(glyphs, features = {}) {
239298
- for (let chain2 of this.morx.chains) {
239299
- let flags = chain2.defaultFlags;
239300
- 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) {
239301
239925
  let f2;
239302
239926
  if (f2 = features[feature.featureType]) {
239303
239927
  if (f2[feature.featureSetting]) {
@@ -239309,7 +239933,7 @@ class $55f71433a605c87d$export$2e2bcd8739ae039 {
239309
239933
  }
239310
239934
  }
239311
239935
  }
239312
- for (let subtable of chain2.subtables)
239936
+ for (let subtable of chain.subtables)
239313
239937
  if (subtable.subFeatureFlags & flags)
239314
239938
  this.processSubtable(subtable, glyphs);
239315
239939
  }
@@ -239456,8 +240080,8 @@ class $55f71433a605c87d$export$2e2bcd8739ae039 {
239456
240080
  }
239457
240081
  getSupportedFeatures() {
239458
240082
  let features = [];
239459
- for (let chain2 of this.morx.chains)
239460
- for (let feature of chain2.features)
240083
+ for (let chain of this.morx.chains)
240084
+ for (let feature of chain.features)
239461
240085
  features.push([
239462
240086
  feature.featureType,
239463
240087
  feature.featureSetting
@@ -239471,9 +240095,9 @@ class $55f71433a605c87d$export$2e2bcd8739ae039 {
239471
240095
  }
239472
240096
  generateInputCache() {
239473
240097
  this.inputCache = {};
239474
- for (let chain2 of this.morx.chains) {
239475
- let flags = chain2.defaultFlags;
239476
- 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)
239477
240101
  if (subtable.subFeatureFlags & flags)
239478
240102
  this.generateInputsForSubtable(subtable);
239479
240103
  }
@@ -260556,6 +261180,7 @@ function buildAgentCoordinator(input) {
260556
261180
  const indicators = mergeIndicators(cfg.linear.indicators, args.indicators);
260557
261181
  const team = args.linearTeam || cfg.linear.team;
260558
261182
  const assignee = args.linearAssignee || cfg.linear.assignee;
261183
+ const ticketNumbers = resolveTicketNumbers(args.ticketTokens, team);
260559
261184
  const excludeFromTodo = unionMarkers(indicators.setDone, indicators.setError);
260560
261185
  const gitRunner = input.runners?.git ?? bunGitRunner;
260561
261186
  const cmdRunner = input.runners?.cmd ?? bunCmdRunner;
@@ -260587,7 +261212,19 @@ function buildAgentCoordinator(input) {
260587
261212
  }
260588
261213
  return code;
260589
261214
  });
260590
- 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
+ }
260591
261228
  const prDiscovery = createPrDiscovery({
260592
261229
  apiKey,
260593
261230
  projectRoot,
@@ -260623,6 +261260,7 @@ function buildAgentCoordinator(input) {
260623
261260
  onLog,
260624
261261
  diag,
260625
261262
  cwdByChange,
261263
+ ...ticketNumbers.length > 0 ? { ticketNumbers } : {},
260626
261264
  stalePingedAt,
260627
261265
  lastHandledReviewActivity,
260628
261266
  resolvePrUrlForIssue: prDiscovery.resolvePrUrlForIssue
@@ -260719,7 +261357,7 @@ function buildAgentCoordinator(input) {
260719
261357
  fetchTodo: () => resolvers.fetchByGet(indicators.getTodo, excludeFromTodo),
260720
261358
  fetchInProgress: () => resolvers.fetchByGet(indicators.getInProgress, unionMarkers(indicators.setError)),
260721
261359
  fetchMentions,
260722
- fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, indicators),
261360
+ fetchDoneCandidates: () => fetchDoneCandidatesWith(apiKey, team, assignee, indicators, ticketNumbers.length > 0 ? ticketNumbers : undefined),
260723
261361
  prepare: prep.prepare,
260724
261362
  prepareTaskForTrigger: prep.prepareTaskForTrigger,
260725
261363
  spawnWorker,
@@ -260805,6 +261443,7 @@ var init_wire = __esm(() => {
260805
261443
  init_indicators();
260806
261444
  init_task_bodies();
260807
261445
  init_linear_resolvers();
261446
+ init_linear_client();
260808
261447
  init_prepare();
260809
261448
  init_pr_discovery();
260810
261449
  init_mention_scan();
@@ -260820,7 +261459,7 @@ import { dirname as dirname15 } from "path";
260820
261459
  function createJsonLogFileSink(path) {
260821
261460
  if (!path)
260822
261461
  return { emit: () => {} };
260823
- let chain2 = (async () => {
261462
+ let chain = (async () => {
260824
261463
  try {
260825
261464
  await mkdir12(dirname15(path), { recursive: true });
260826
261465
  await Bun.write(path, "");
@@ -260830,7 +261469,7 @@ function createJsonLogFileSink(path) {
260830
261469
  emit(event) {
260831
261470
  const line = JSON.stringify({ ts: Date.now(), v: VERSION, ...event }) + `
260832
261471
  `;
260833
- chain2 = chain2.then(async () => {
261472
+ chain = chain.then(async () => {
260834
261473
  try {
260835
261474
  await appendFile2(path, line);
260836
261475
  } catch {}
@@ -262938,6 +263577,18 @@ var init_tmux = __esm(() => {
262938
263577
  decoder = new TextDecoder;
262939
263578
  });
262940
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
+
262941
263592
  // apps/agent/src/list-sort.ts
262942
263593
  function assignTier(status) {
262943
263594
  if (status === null || status.kind === "error")
@@ -262960,7 +263611,7 @@ function createdAtOf(status) {
262960
263611
  return "";
262961
263612
  }
262962
263613
  function sortRows(rows) {
262963
- 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) => {
262964
263615
  const ia = a.issueCreatedAt;
262965
263616
  const ib = b2.issueCreatedAt;
262966
263617
  if (ia === ib)
@@ -262976,7 +263627,7 @@ function sortRows(rows) {
262976
263627
  if (ca === cb)
262977
263628
  return 0;
262978
263629
  return ca < cb ? -1 : 1;
262979
- }, (a, b2) => a.bucketOrder - b2.bucketOrder, (a, b2) => a.identifier.localeCompare(b2.identifier));
263630
+ }, (a, b2) => a.identifier.localeCompare(b2.identifier));
262980
263631
  return [...rows].sort(cmp);
262981
263632
  }
262982
263633
  var init_list_sort = () => {};
@@ -263025,7 +263676,8 @@ __export(exports_list, {
263025
263676
  formatReviewCell: () => formatReviewCell,
263026
263677
  formatPrStatusMarker: () => formatPrStatusMarker,
263027
263678
  formatBlockedCell: () => formatBlockedCell,
263028
- buildBuckets: () => buildBuckets
263679
+ buildBuckets: () => buildBuckets,
263680
+ backlogRankByIssueId: () => backlogRankByIssueId
263029
263681
  });
263030
263682
  import { join as join36 } from "path";
263031
263683
  function countTaskItems(content) {
@@ -263127,14 +263779,15 @@ function buildBuckets(indicators) {
263127
263779
  { label: "auto-merge", indicator: indicators.getAutoMerge, exclude: [] }
263128
263780
  ];
263129
263781
  }
263130
- async function fetchBucketIssues(apiKey, bucket, team, assignee) {
263782
+ async function fetchBucketIssues(apiKey, bucket, team, assignee, ticketNumbers) {
263131
263783
  if (!bucket.indicator || bucket.indicator.filter.length === 0)
263132
263784
  return [];
263133
263785
  const spec = {
263134
263786
  team,
263135
263787
  assignee,
263136
263788
  include: bucket.indicator.filter,
263137
- exclude: bucket.exclude
263789
+ exclude: bucket.exclude,
263790
+ ...ticketNumbers.length > 0 ? { numbers: ticketNumbers } : {}
263138
263791
  };
263139
263792
  return fetchOpenIssues(apiKey, spec);
263140
263793
  }
@@ -263178,13 +263831,19 @@ function formatPrStatusMarker(status, failedCheckNames) {
263178
263831
  return "ok";
263179
263832
  return parts.join(" ");
263180
263833
  }
263181
- 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 = []) {
263182
263841
  const bucketResults = await Promise.all(buckets.map(async (bucket) => {
263183
263842
  if (!bucket.indicator || bucket.indicator.filter.length === 0) {
263184
263843
  return { bucket, issues: [], error: null };
263185
263844
  }
263186
263845
  try {
263187
- const issues = await fetchBucketIssues(apiKey, bucket, team, assignee);
263846
+ const issues = await fetchBucketIssues(apiKey, bucket, team, assignee, ticketNumbers);
263188
263847
  return { bucket, issues, error: null };
263189
263848
  } catch (err) {
263190
263849
  return {
@@ -263202,16 +263861,17 @@ ${bucket.label}: error fetching from Linear \u2014 ${error48}
263202
263861
  }
263203
263862
  }
263204
263863
  const seen = new Map;
263205
- let order = 0;
263864
+ const issueById = new Map;
263206
263865
  for (const { bucket, issues } of bucketResults) {
263207
263866
  for (const issue2 of issues) {
263208
263867
  if (seen.has(issue2.id))
263209
263868
  continue;
263869
+ issueById.set(issue2.id, issue2);
263210
263870
  seen.set(issue2.id, {
263211
263871
  issueId: issue2.id,
263212
263872
  identifier: issue2.identifier,
263213
263873
  status: null,
263214
- bucketOrder: order++,
263874
+ bucketOrder: 0,
263215
263875
  issueCreatedAt: issue2.createdAt,
263216
263876
  bucketLabel: bucket.label,
263217
263877
  stateName: issue2.state.name,
@@ -263222,6 +263882,9 @@ ${bucket.label}: error fetching from Linear \u2014 ${error48}
263222
263882
  }
263223
263883
  }
263224
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;
263225
263888
  try {
263226
263889
  const attachmentsByIssue = await fetchAttachmentsForIssues(apiKey, rows.map((r) => r.issueId));
263227
263890
  for (const row of rows) {
@@ -263338,6 +264001,15 @@ Linear: LINEAR_API_KEY not set \u2014 cannot fetch tickets. Configured buckets:
263338
264001
  }
263339
264002
  return;
263340
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
+ }
263341
264013
  if (team)
263342
264014
  process.stdout.write(`
263343
264015
  team: ${team}
@@ -263345,13 +264017,21 @@ team: ${team}
263345
264017
  if (assignee)
263346
264018
  process.stdout.write(`assignee: ${assignee}
263347
264019
  `);
263348
- 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);
263349
264024
  }
263350
264025
  function normalizeIdentifier(input) {
263351
- const match = input.match(/^([A-Za-z]+)-(\d+)(?:-.*)?$/);
263352
- if (!match)
264026
+ let parsed;
264027
+ try {
264028
+ parsed = parseTicketIdentifier(input);
264029
+ } catch {
264030
+ return null;
264031
+ }
264032
+ if (parsed.teamKey === null)
263353
264033
  return null;
263354
- return `${match[1].toUpperCase()}-${match[2]}`;
264034
+ return `${parsed.teamKey}-${parsed.number}`;
263355
264035
  }
263356
264036
  async function fetchIssueByIdentifier(apiKey, identifier) {
263357
264037
  const match = identifier.match(/^([A-Z]+)-(\d+)$/);
@@ -263501,6 +264181,8 @@ var init_list = __esm(() => {
263501
264181
  init_pr_status();
263502
264182
  init_pr_url();
263503
264183
  init_list_sort();
264184
+ init_ordering();
264185
+ init_queue_order();
263504
264186
  init_ci();
263505
264187
  init_linear_client();
263506
264188
  init_indicators();
@@ -263810,7 +264492,8 @@ async function main3(argv) {
263810
264492
  debug: args.debug,
263811
264493
  name: args.name,
263812
264494
  checks: args.checks,
263813
- review: args.review
264495
+ review: args.review,
264496
+ ticketTokens: args.ticketTokens
263814
264497
  });
263815
264498
  });
263816
264499
  return typeof process.exitCode === "number" ? process.exitCode : 0;
@@ -263830,6 +264513,19 @@ async function main3(argv) {
263830
264513
  `);
263831
264514
  return 0;
263832
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
+ }
263833
264529
  await mkdir15(statesDir, { recursive: true });
263834
264530
  await mkdir15(tasksDir, { recursive: true });
263835
264531
  await mkdir15(join39(projectRoot, ".ralph"), { recursive: true });