@inteeka/task-cli 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -301,6 +301,7 @@ async function clearCredentials() {
301
301
  if (err.code !== "ENOENT") throw err;
302
302
  }
303
303
  }
304
+ var CREDENTIALS_DIR = CONFIG_DIR;
304
305
 
305
306
  // src/util/host.ts
306
307
  import { createHash } from "crypto";
@@ -451,13 +452,34 @@ function sleep(ms) {
451
452
 
452
453
  // src/api/client.ts
453
454
  import { request as request3 } from "undici";
454
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
455
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
455
456
  import { homedir as homedir2 } from "os";
456
- import { join as join2 } from "path";
457
+ import { join as join3 } from "path";
457
458
 
458
459
  // src/auth/refresh.ts
459
460
  import { request as request2 } from "undici";
461
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
462
+ import { join as join2 } from "path";
463
+ import { lock } from "proper-lockfile";
460
464
  var REFRESH_LEEWAY_MS = 6e4;
465
+ var REFRESH_LOCK_PATH = join2(CREDENTIALS_DIR, ".refresh.lock");
466
+ async function ensureLockFileExists() {
467
+ await mkdir2(CREDENTIALS_DIR, { recursive: true, mode: 448 });
468
+ await writeFile2(REFRESH_LOCK_PATH, "", { mode: 384, flag: "a" });
469
+ }
470
+ async function withRefreshLock(fn) {
471
+ await ensureLockFileExists();
472
+ const release = await lock(REFRESH_LOCK_PATH, {
473
+ retries: { retries: 50, minTimeout: 100, maxTimeout: 1e3, factor: 1.5 },
474
+ stale: 3e4,
475
+ update: 1e4
476
+ });
477
+ try {
478
+ return await fn();
479
+ } finally {
480
+ await release();
481
+ }
482
+ }
461
483
  async function ensureFreshAccessToken(creds) {
462
484
  const expiresMs = new Date(creds.access_expires_at).getTime();
463
485
  if (Number.isFinite(expiresMs) && expiresMs - Date.now() > REFRESH_LEEWAY_MS) {
@@ -466,6 +488,18 @@ async function ensureFreshAccessToken(creds) {
466
488
  return performRefresh(creds);
467
489
  }
468
490
  async function performRefresh(creds) {
491
+ return withRefreshLock(async () => {
492
+ const onDisk = await readCredentials();
493
+ if (onDisk) {
494
+ const expiresMs = new Date(onDisk.access_expires_at).getTime();
495
+ if (Number.isFinite(expiresMs) && expiresMs - Date.now() > REFRESH_LEEWAY_MS) {
496
+ return onDisk;
497
+ }
498
+ }
499
+ return refreshHttp(onDisk ?? creds);
500
+ });
501
+ }
502
+ async function refreshHttp(creds, retriesLeft = 1) {
469
503
  const { hostId } = getHostInfo();
470
504
  const apiUrl = creds.api_url.replace(/\/$/, "");
471
505
  const res = await request2(`${apiUrl}/api/v1/cli/auth/refresh`, {
@@ -483,6 +517,16 @@ async function performRefresh(creds) {
483
517
  headersTimeout: 15e3
484
518
  });
485
519
  if (res.statusCode === 401 || res.statusCode === 403) {
520
+ const onDisk = await readCredentials();
521
+ if (onDisk && onDisk.refresh_token !== creds.refresh_token) {
522
+ const expiresMs = new Date(onDisk.access_expires_at).getTime();
523
+ if (Number.isFinite(expiresMs) && expiresMs - Date.now() > REFRESH_LEEWAY_MS) {
524
+ return onDisk;
525
+ }
526
+ if (retriesLeft > 0) {
527
+ return refreshHttp(onDisk, retriesLeft - 1);
528
+ }
529
+ }
486
530
  await clearCredentials();
487
531
  throw new CliError(
488
532
  CLI_EXIT_CODES.UNAUTHORISED,
@@ -520,13 +564,13 @@ async function manualRefresh() {
520
564
  // src/api/client.ts
521
565
  async function dumpServerError(method, path, status, rawBody, headers) {
522
566
  try {
523
- const dir = join2(homedir2(), ".cache", "task", "api-debug");
524
- await mkdir2(dir, { recursive: true });
525
- const file = join2(dir, `${Date.now()}-${status}.log`);
567
+ const dir = join3(homedir2(), ".cache", "task", "api-debug");
568
+ await mkdir3(dir, { recursive: true });
569
+ const file = join3(dir, `${Date.now()}-${status}.log`);
526
570
  const safeHeaders = { ...headers };
527
571
  delete safeHeaders["Authorization"];
528
572
  delete safeHeaders["authorization"];
529
- await writeFile2(
573
+ await writeFile3(
530
574
  file,
531
575
  [
532
576
  `## ${method} ${path}`,
@@ -668,10 +712,10 @@ async function apiCallOrThrow(method, path, options = {}) {
668
712
  }
669
713
 
670
714
  // src/config/local-config.ts
671
- import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
715
+ import { mkdir as mkdir4, readFile as readFile2, writeFile as writeFile4 } from "fs/promises";
672
716
  import { homedir as homedir3 } from "os";
673
- import { dirname as dirname2, join as join3 } from "path";
674
- var CONFIG_PATH = join3(homedir3(), ".config", "task", "config.json");
717
+ import { dirname as dirname2, join as join4 } from "path";
718
+ var CONFIG_PATH = join4(homedir3(), ".config", "task", "config.json");
675
719
  var DEFAULT_CONFIG = {
676
720
  api_url: process.env["TASK_API_URL"] ?? "http://localhost:3400",
677
721
  default_project: null,
@@ -691,8 +735,8 @@ async function readLocalConfig() {
691
735
  }
692
736
  }
693
737
  async function writeLocalConfig(config) {
694
- await mkdir3(dirname2(CONFIG_PATH), { recursive: true });
695
- await writeFile3(CONFIG_PATH, JSON.stringify(config, null, 2));
738
+ await mkdir4(dirname2(CONFIG_PATH), { recursive: true });
739
+ await writeFile4(CONFIG_PATH, JSON.stringify(config, null, 2));
696
740
  }
697
741
  async function setConfigValue(key, value) {
698
742
  const cfg = await readLocalConfig();
@@ -811,14 +855,14 @@ function registerAuthRefresh(program2) {
811
855
  }
812
856
 
813
857
  // src/commands/link.ts
814
- import { readFile as readFile4, writeFile as writeFile5, appendFile, access } from "fs/promises";
858
+ import { readFile as readFile4, writeFile as writeFile6, appendFile, access } from "fs/promises";
815
859
  import { constants as fsConstants } from "fs";
816
- import { join as join5 } from "path";
860
+ import { join as join6 } from "path";
817
861
  import inquirer from "inquirer";
818
862
 
819
863
  // src/config/project.ts
820
- import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
821
- import { dirname as dirname3, join as join4, resolve } from "path";
864
+ import { mkdir as mkdir5, readFile as readFile3, writeFile as writeFile5, unlink as unlink2 } from "fs/promises";
865
+ import { dirname as dirname3, join as join5, resolve } from "path";
822
866
  import { execSync } from "child_process";
823
867
  function findRepoRoot(start = process.cwd()) {
824
868
  try {
@@ -829,7 +873,7 @@ function findRepoRoot(start = process.cwd()) {
829
873
  }
830
874
  }
831
875
  function configPath(repoRoot) {
832
- return join4(repoRoot ?? findRepoRoot(), ".task", "config.json");
876
+ return join5(repoRoot ?? findRepoRoot(), ".task", "config.json");
833
877
  }
834
878
  async function readProjectConfig(repoRoot) {
835
879
  const path = configPath(repoRoot);
@@ -843,8 +887,8 @@ async function readProjectConfig(repoRoot) {
843
887
  }
844
888
  async function writeProjectConfig(config, repoRoot) {
845
889
  const path = configPath(repoRoot);
846
- await mkdir4(dirname3(path), { recursive: true });
847
- await writeFile4(path, JSON.stringify(config, null, 2));
890
+ await mkdir5(dirname3(path), { recursive: true });
891
+ await writeFile5(path, JSON.stringify(config, null, 2));
848
892
  }
849
893
  async function clearProjectConfig(repoRoot) {
850
894
  const path = configPath(repoRoot);
@@ -943,7 +987,7 @@ async function resolveProject(projects, opts) {
943
987
  return picked;
944
988
  }
945
989
  async function ensureGitignored(repoRoot) {
946
- const gitignorePath = join5(repoRoot, ".gitignore");
990
+ const gitignorePath = join6(repoRoot, ".gitignore");
947
991
  let existing = null;
948
992
  try {
949
993
  await access(gitignorePath, fsConstants.F_OK);
@@ -958,7 +1002,7 @@ async function ensureGitignored(repoRoot) {
958
1002
  await appendFile(gitignorePath, block);
959
1003
  return "added";
960
1004
  }
961
- await writeFile5(gitignorePath, "# task CLI link config\n.task/\n");
1005
+ await writeFile6(gitignorePath, "# task CLI link config\n.task/\n");
962
1006
  return "created";
963
1007
  }
964
1008
 
@@ -1470,9 +1514,9 @@ import inquirer2 from "inquirer";
1470
1514
 
1471
1515
  // src/agent/agent-service.ts
1472
1516
  import { spawn } from "child_process";
1473
- import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
1517
+ import { mkdir as mkdir6, writeFile as writeFile7 } from "fs/promises";
1474
1518
  import { homedir as homedir4 } from "os";
1475
- import { join as join6 } from "path";
1519
+ import { join as join7 } from "path";
1476
1520
 
1477
1521
  // src/agent/allowed-tools.ts
1478
1522
  var ALLOWED_TOOLS = CLI_ALLOWED_TOOLS;
@@ -1529,10 +1573,10 @@ async function runAgent(args) {
1529
1573
  let outputLogPath = null;
1530
1574
  let logHandle = null;
1531
1575
  if (args.silent) {
1532
- const dir = join6(homedir4(), ".cache", "task", "runs");
1533
- await mkdir5(dir, { recursive: true });
1534
- outputLogPath = join6(dir, `${args.runId}.log`);
1535
- await writeFile6(outputLogPath, "");
1576
+ const dir = join7(homedir4(), ".cache", "task", "runs");
1577
+ await mkdir6(dir, { recursive: true });
1578
+ outputLogPath = join7(dir, `${args.runId}.log`);
1579
+ await writeFile7(outputLogPath, "");
1536
1580
  const { createWriteStream } = await import("fs");
1537
1581
  logHandle = createWriteStream(outputLogPath, { flags: "a" });
1538
1582
  }
@@ -1733,11 +1777,111 @@ async function runProjectTest(args) {
1733
1777
  });
1734
1778
  }
1735
1779
 
1780
+ // src/test-runner/auto-install.ts
1781
+ import { spawn as spawn3 } from "child_process";
1782
+ import { stat as stat2 } from "fs/promises";
1783
+ import { dirname as dirname4, join as join8 } from "path";
1784
+ var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
1785
+ var TAIL_BYTES2 = 4e3;
1786
+ async function findPnpmWorkspaceRoot(start) {
1787
+ let cur = start;
1788
+ for (; ; ) {
1789
+ try {
1790
+ await stat2(join8(cur, "pnpm-lock.yaml"));
1791
+ return cur;
1792
+ } catch {
1793
+ }
1794
+ const parent = dirname4(cur);
1795
+ if (parent === cur) return null;
1796
+ cur = parent;
1797
+ }
1798
+ }
1799
+ async function mtimeMs(path) {
1800
+ try {
1801
+ return (await stat2(path)).mtimeMs;
1802
+ } catch {
1803
+ return null;
1804
+ }
1805
+ }
1806
+ async function ensureWorkspaceInstalled(args) {
1807
+ const start = Date.now();
1808
+ const workspaceRoot = await findPnpmWorkspaceRoot(args.cwd);
1809
+ if (!workspaceRoot) {
1810
+ return {
1811
+ ok: true,
1812
+ skipped: true,
1813
+ reason: "no pnpm-lock.yaml on the path \u2014 not a pnpm workspace",
1814
+ workspaceRoot: null,
1815
+ durationMs: Date.now() - start,
1816
+ exitCode: null,
1817
+ tail: ""
1818
+ };
1819
+ }
1820
+ const lockMtime = await mtimeMs(join8(workspaceRoot, "pnpm-lock.yaml"));
1821
+ const markerMtime = await mtimeMs(join8(workspaceRoot, "node_modules", ".modules.yaml"));
1822
+ if (lockMtime !== null && markerMtime !== null && markerMtime >= lockMtime) {
1823
+ return {
1824
+ ok: true,
1825
+ skipped: true,
1826
+ reason: "node_modules in sync with pnpm-lock.yaml",
1827
+ workspaceRoot,
1828
+ durationMs: Date.now() - start,
1829
+ exitCode: null,
1830
+ tail: ""
1831
+ };
1832
+ }
1833
+ const reason = markerMtime === null ? "node_modules missing \u2014 cold install" : "pnpm-lock.yaml newer than install marker";
1834
+ return new Promise((resolve2) => {
1835
+ const child = spawn3("pnpm", ["install", "--frozen-lockfile"], {
1836
+ cwd: workspaceRoot,
1837
+ stdio: ["ignore", "pipe", "pipe"],
1838
+ shell: false,
1839
+ env: { ...process.env, CI: "1" },
1840
+ ...args.signal ? { signal: args.signal } : {}
1841
+ });
1842
+ let buf = "";
1843
+ const append = (chunk) => {
1844
+ buf += chunk.toString("utf8");
1845
+ if (buf.length > TAIL_BYTES2 * 2) buf = buf.slice(-TAIL_BYTES2);
1846
+ };
1847
+ child.stdout?.on("data", append);
1848
+ child.stderr?.on("data", append);
1849
+ const timeoutHandle = setTimeout(() => {
1850
+ child.kill("SIGKILL");
1851
+ }, INSTALL_TIMEOUT_MS);
1852
+ child.on("close", (code) => {
1853
+ clearTimeout(timeoutHandle);
1854
+ resolve2({
1855
+ ok: code === 0,
1856
+ skipped: false,
1857
+ reason,
1858
+ workspaceRoot,
1859
+ durationMs: Date.now() - start,
1860
+ exitCode: code,
1861
+ tail: buf.slice(-TAIL_BYTES2)
1862
+ });
1863
+ });
1864
+ child.on("error", (err) => {
1865
+ clearTimeout(timeoutHandle);
1866
+ const msg = err.code === "ENOENT" ? "`pnpm` not on PATH" : err.message;
1867
+ resolve2({
1868
+ ok: false,
1869
+ skipped: false,
1870
+ reason: `${reason} \u2014 spawn error: ${msg}`,
1871
+ workspaceRoot,
1872
+ durationMs: Date.now() - start,
1873
+ exitCode: null,
1874
+ tail: buf.slice(-TAIL_BYTES2)
1875
+ });
1876
+ });
1877
+ });
1878
+ }
1879
+
1736
1880
  // src/util/progress.ts
1737
- import { mkdir as mkdir6, writeFile as writeFile7, rename as rename2, unlink as unlink3, readdir, stat as stat2 } from "fs/promises";
1881
+ import { mkdir as mkdir7, writeFile as writeFile8, rename as rename2, unlink as unlink3, readdir, stat as stat3 } from "fs/promises";
1738
1882
  import { tmpdir } from "os";
1739
- import { join as join7 } from "path";
1740
- var PROGRESS_DIR = join7(tmpdir(), "task-progress");
1883
+ import { join as join9 } from "path";
1884
+ var PROGRESS_DIR = join9(tmpdir(), "task-progress");
1741
1885
  var STALE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
1742
1886
  var ProgressWriter = class {
1743
1887
  path;
@@ -1750,7 +1894,7 @@ var ProgressWriter = class {
1750
1894
  this.ticketId = ticketId;
1751
1895
  const deliveryId = process.env["TASK_DELIVERY_ID"]?.trim();
1752
1896
  const fileBase = deliveryId && deliveryId.length > 0 ? deliveryId : `manual-${process.pid}`;
1753
- this.path = join7(PROGRESS_DIR, `${fileBase}.json`);
1897
+ this.path = join9(PROGRESS_DIR, `${fileBase}.json`);
1754
1898
  }
1755
1899
  /** Switch the in-flight ticket between phases (used by `task scan` which iterates). */
1756
1900
  setTicketId(ticketId) {
@@ -1784,12 +1928,12 @@ var ProgressWriter = class {
1784
1928
  });
1785
1929
  }
1786
1930
  async writeAtomic(payload) {
1787
- await mkdir6(PROGRESS_DIR, { recursive: true }).catch(() => {
1931
+ await mkdir7(PROGRESS_DIR, { recursive: true }).catch(() => {
1788
1932
  });
1789
1933
  const body = JSON.stringify(payload);
1790
1934
  const tmp = `${this.path}.tmp`;
1791
1935
  try {
1792
- await writeFile7(tmp, body, { encoding: "utf8", mode: 384 });
1936
+ await writeFile8(tmp, body, { encoding: "utf8", mode: 384 });
1793
1937
  await rename2(tmp, this.path);
1794
1938
  } catch {
1795
1939
  await unlink3(tmp).catch(() => {
@@ -1806,9 +1950,9 @@ var ProgressWriter = class {
1806
1950
  const cutoff = Date.now() - STALE_MAX_AGE_MS;
1807
1951
  await Promise.all(
1808
1952
  entries.filter((name) => name.endsWith(".json") || name.endsWith(".json.tmp")).map(async (name) => {
1809
- const p = join7(PROGRESS_DIR, name);
1953
+ const p = join9(PROGRESS_DIR, name);
1810
1954
  try {
1811
- const s = await stat2(p);
1955
+ const s = await stat3(p);
1812
1956
  if (s.mtimeMs < cutoff) {
1813
1957
  await unlink3(p).catch(() => {
1814
1958
  });
@@ -2172,6 +2316,50 @@ ${detail.ai_fix_approval_notes}` : ""
2172
2316
  await progress.setPhase("testing", {
2173
2317
  detail: testCommand ?? "pnpm typecheck"
2174
2318
  });
2319
+ const installResult = await ensureWorkspaceInstalled({ cwd });
2320
+ if (!installResult.ok) {
2321
+ discardWorkingTreeChanges(cwd);
2322
+ try {
2323
+ checkoutBranch(cwd, baseBranch);
2324
+ } catch {
2325
+ }
2326
+ deleteLocalBranch(cwd, branchName);
2327
+ await apiCall("POST", "/api/v1/cli/me/runs", {
2328
+ body: {
2329
+ ticket_id: detail.id,
2330
+ schedule_id: opts.scheduleId,
2331
+ event: "tests_failed",
2332
+ claude_session_id: runId,
2333
+ duration_ms: installResult.durationMs,
2334
+ output_excerpt: `pnpm install --frozen-lockfile failed (${installResult.reason})
2335
+ ${installResult.tail}`.slice(
2336
+ 0,
2337
+ 4e3
2338
+ )
2339
+ }
2340
+ });
2341
+ if (!silent) {
2342
+ process.stdout.write(
2343
+ `${c.err("\u2717 pnpm install --frozen-lockfile failed")} (exit ${installResult.exitCode}) \u2014 branch deleted, no push.
2344
+ `
2345
+ );
2346
+ if (installResult.tail.trim().length > 0) {
2347
+ process.stdout.write(c.dim(installResult.tail.slice(-1e3) + "\n"));
2348
+ }
2349
+ }
2350
+ throw new CliError(
2351
+ CLI_EXIT_CODES.GENERIC_ERROR,
2352
+ `pnpm install --frozen-lockfile failed (exit ${installResult.exitCode}) \u2014 ${installResult.reason}`
2353
+ );
2354
+ }
2355
+ if (!installResult.skipped && !silent) {
2356
+ process.stdout.write(
2357
+ c.dim(
2358
+ ` pnpm install --frozen-lockfile (${installResult.reason}) \u2014 ${installResult.durationMs}ms
2359
+ `
2360
+ )
2361
+ );
2362
+ }
2175
2363
  if (!silent)
2176
2364
  process.stdout.write(c.dim(` running pre-push test: ${testCommand ?? "pnpm typecheck"}
2177
2365
  `));
@@ -3199,10 +3387,10 @@ function autopilotExitCode(code, status) {
3199
3387
  }
3200
3388
 
3201
3389
  // src/scan/llm.ts
3202
- import { spawn as spawn3 } from "child_process";
3203
- import { mkdir as mkdir7, writeFile as writeFile8 } from "fs/promises";
3390
+ import { spawn as spawn4 } from "child_process";
3391
+ import { mkdir as mkdir8, writeFile as writeFile9 } from "fs/promises";
3204
3392
  import { homedir as homedir5 } from "os";
3205
- import { join as join8 } from "path";
3393
+ import { join as join10 } from "path";
3206
3394
  var FIX_PROMPT_JSON_SCHEMA = {
3207
3395
  type: "object",
3208
3396
  // Phase 3 — confidence_reason is REQUIRED unconditionally so the
@@ -3291,7 +3479,7 @@ async function generateFixPromptJson(args) {
3291
3479
  return new Promise((resolve2, reject) => {
3292
3480
  let child;
3293
3481
  try {
3294
- child = spawn3(claude, cliArgs, {
3482
+ child = spawn4(claude, cliArgs, {
3295
3483
  stdio: ["pipe", "pipe", "pipe"],
3296
3484
  signal: args.signal
3297
3485
  });
@@ -3417,10 +3605,10 @@ function readEnvelopeTokens(raw, userPrompt, innerText) {
3417
3605
  async function maybeDumpDebug(ticketId, stdout, stderr) {
3418
3606
  if (!DEBUG && stdout.length === 0 && stderr.length === 0) return null;
3419
3607
  try {
3420
- const dir = join8(homedir5(), ".cache", "task", "scan-debug");
3421
- await mkdir7(dir, { recursive: true });
3422
- const path = join8(dir, `${ticketId}-${Date.now()}.log`);
3423
- await writeFile8(
3608
+ const dir = join10(homedir5(), ".cache", "task", "scan-debug");
3609
+ await mkdir8(dir, { recursive: true });
3610
+ const path = join10(dir, `${ticketId}-${Date.now()}.log`);
3611
+ await writeFile9(
3424
3612
  path,
3425
3613
  ["## ticket_id", ticketId, "", "## stdout", stdout, "", "## stderr", stderr].join("\n")
3426
3614
  );
@@ -3667,10 +3855,13 @@ ${c.bold(`${project.organisation_slug}/${project.project_slug}`)} ${c.dim(`(${pr
3667
3855
  for (const ticket of prepared.tickets) {
3668
3856
  if (args.isInterrupted()) break;
3669
3857
  inFlight.add(ticket.ticket_id);
3670
- const spinner = silent ? null : ora2(`#${ticket.sequence_number} ${ticket.title.slice(0, 60)}`).start();
3858
+ const specialistSuffix = ticket.specialist ? ` \xB7 Specialist: ${ticket.specialist.name} (${ticket.specialist.skill_count} skill${ticket.specialist.skill_count === 1 ? "" : "s"})` : "";
3859
+ const spinner = silent ? null : ora2(
3860
+ `#${ticket.sequence_number} ${ticket.title.slice(0, 60)}${specialistSuffix}`
3861
+ ).start();
3671
3862
  progress.setTicketId(ticket.ticket_id);
3672
3863
  await progress.setPhase("analysing", {
3673
- detail: `Generating fix prompt for #${ticket.sequence_number}`
3864
+ detail: ticket.specialist ? `Generating fix prompt for #${ticket.sequence_number} as ${ticket.specialist.name}` : `Generating fix prompt for #${ticket.sequence_number}`
3674
3865
  });
3675
3866
  try {
3676
3867
  const generated = await safeGenerate(ticket, claudePath);
@@ -3760,6 +3951,252 @@ function clampInt(raw, min, max, fallback) {
3760
3951
  return Math.min(v, max);
3761
3952
  }
3762
3953
 
3954
+ // src/commands/fast-track.ts
3955
+ import { randomUUID as randomUUID3 } from "crypto";
3956
+ import ora3 from "ora";
3957
+ function registerFastTrack(program2) {
3958
+ program2.command("fast-track").description(
3959
+ "End-to-end: scan + auto-approve + work on the next CLI-eligible ticket(s) in the linked project \u2014 no admin review step"
3960
+ ).option("--max <n>", "Process up to N tickets in this invocation", "1").option("--api-url <url>", "Override TASK_API_URL").option("--silent", "Suppress per-ticket progress chrome").option("--dry-run", "Run scan + approve + agent + tests but do not commit, push, or open a PR").option(
3961
+ "--reset",
3962
+ "DESTRUCTIVE: discard local working-tree changes before the first ticket. Requires --confirm in non-TTY contexts."
3963
+ ).option("--confirm", "Confirm --reset in non-TTY (silent / scheduled-task) contexts").option("--schedule-id <id>", "Internal: schedule id when invoked from a scheduled task").action(async (opts) => {
3964
+ await runFastTrack(opts);
3965
+ });
3966
+ }
3967
+ async function runFastTrack(opts) {
3968
+ let creds = await readCredentials();
3969
+ if (!creds) {
3970
+ throw new CliError(
3971
+ CLI_EXIT_CODES.MISCONFIGURATION,
3972
+ "Not signed in",
3973
+ "Run 'task login' to authenticate."
3974
+ );
3975
+ }
3976
+ creds = await ensureFreshAccessToken(creds);
3977
+ const localCfg = await readLocalConfig();
3978
+ const linkedProject = await readProjectConfig(findRepoRoot());
3979
+ if (!linkedProject) {
3980
+ throw new CliError(
3981
+ CLI_EXIT_CODES.MISCONFIGURATION,
3982
+ "No project link in this repo",
3983
+ "Run 'task link' first \u2014 fast-track operates on the linked project only."
3984
+ );
3985
+ }
3986
+ const apiUrl = (opts.apiUrl ?? process.env["TASK_API_URL"] ?? creds.api_url ?? localCfg.api_url ?? linkedProject.api_url ?? "http://localhost:3400").replace(/\/$/, "");
3987
+ const max = Math.max(1, parseInt(opts.max, 10) || 1);
3988
+ const silent = !!opts.silent || localCfg.silent;
3989
+ const ctx = await buildWorkContext({ max: "1", silent: opts.silent });
3990
+ const api = new AutopilotApi({ apiUrl, creds });
3991
+ const claudePath = localCfg.claude_path ?? void 0;
3992
+ let firstIteration = true;
3993
+ const results = [];
3994
+ for (let i = 0; i < max; i++) {
3995
+ const innerWorkOpts = {
3996
+ auto: false,
3997
+ // We pin a specific ticketId per iteration.
3998
+ next: false,
3999
+ max: "1",
4000
+ silent: opts.silent,
4001
+ ...opts.dryRun ? { dryRun: true } : {},
4002
+ ...opts.scheduleId ? { scheduleId: opts.scheduleId } : {},
4003
+ ...firstIteration && opts.reset ? { reset: true } : {},
4004
+ ...firstIteration && opts.confirm ? { confirm: true } : {}
4005
+ };
4006
+ const outcome = await fastTrackOneTicket({
4007
+ api,
4008
+ apiUrl,
4009
+ project: linkedProject,
4010
+ ctx,
4011
+ claudePath,
4012
+ silent,
4013
+ innerWorkOpts,
4014
+ scheduleId: opts.scheduleId
4015
+ });
4016
+ if (outcome.kind === "no_eligible") {
4017
+ if (results.length === 0 && !silent) {
4018
+ process.stdout.write(c.dim("No CLI-eligible tickets available to fast-track.\n"));
4019
+ }
4020
+ break;
4021
+ }
4022
+ results.push(outcome.result);
4023
+ firstIteration = false;
4024
+ if (i < max - 1 && outcome.result.status === "completed") {
4025
+ enforceBaseBranchClean(ctx.cwd, ctx.baseBranch);
4026
+ }
4027
+ }
4028
+ if (!silent) {
4029
+ summarise(results);
4030
+ }
4031
+ }
4032
+ async function fastTrackOneTicket(args) {
4033
+ const { api, project, ctx, claudePath, silent, innerWorkOpts, scheduleId } = args;
4034
+ const issued = await api.issueSkillToken({
4035
+ project_id: project.project_id,
4036
+ max_submits: 1
4037
+ });
4038
+ const skillToken = issued.token;
4039
+ const prepared = await api.prepare(skillToken, 1, randomUUID3());
4040
+ if (prepared.tickets.length === 0) {
4041
+ return { kind: "no_eligible" };
4042
+ }
4043
+ const ticket = prepared.tickets[0];
4044
+ const nonce = prepared.prepare_nonce;
4045
+ const spinner = silent ? null : ora3(`#${ticket.sequence_number} ${ticket.title.slice(0, 60)} \u2014 scanning`).start();
4046
+ let generated;
4047
+ try {
4048
+ generated = await generateFixPromptJson({
4049
+ systemPrompt: ticket.system_prompt,
4050
+ repoOverviewBlock: ticket.repo_overview_block,
4051
+ ticketBlock: ticket.ticket_block,
4052
+ outputSchemaHint: ticket.output_schema_hint,
4053
+ modelId: ticket.model_id,
4054
+ ticketId: ticket.ticket_id,
4055
+ ...claudePath ? { claudePath } : {}
4056
+ });
4057
+ } catch (err) {
4058
+ await api.abort(skillToken, [ticket.ticket_id]).catch(() => void 0);
4059
+ const reason = err instanceof LlmGenerationError ? `${err.reason}: ${err.message.slice(0, 200)}` : "";
4060
+ spinner?.fail(`#${ticket.sequence_number} scan failed${reason ? ` (${reason})` : ""}`);
4061
+ throw err instanceof CliError ? err : new CliError(
4062
+ CLI_EXIT_CODES.GENERIC_ERROR,
4063
+ `Fix-prompt generation failed for #${ticket.sequence_number}: ${err.message.slice(0, 200)}`
4064
+ );
4065
+ }
4066
+ const submitted = await api.submit({
4067
+ skillToken,
4068
+ nonce,
4069
+ ticketId: ticket.ticket_id,
4070
+ structured: generated.structured,
4071
+ inputTokens: generated.inputTokens,
4072
+ outputTokens: generated.outputTokens,
4073
+ model: ticket.model_id
4074
+ });
4075
+ if (submitted.status === "skip") {
4076
+ spinner?.warn(`#${ticket.sequence_number} server rejected submission (${submitted.reason})`);
4077
+ return {
4078
+ kind: "processed",
4079
+ result: {
4080
+ sequenceNumber: ticket.sequence_number,
4081
+ ticketId: ticket.ticket_id,
4082
+ previousFixStatus: submitted.reason,
4083
+ status: "scan_skipped",
4084
+ error: submitted.reason
4085
+ }
4086
+ };
4087
+ }
4088
+ const denylistHit = submitted.status === "needs_review";
4089
+ if (spinner) {
4090
+ spinner.text = `#${ticket.sequence_number} approving${denylistHit ? " (denylist override)" : ""}`;
4091
+ }
4092
+ let approved;
4093
+ try {
4094
+ approved = await apiCallOrThrow(
4095
+ "POST",
4096
+ `/api/v1/cli/me/tickets/${ticket.ticket_id}/ai-fix-prompt/fast-approve`,
4097
+ {
4098
+ body: {
4099
+ ...denylistHit ? { denylist_acknowledged: true } : {},
4100
+ reason: `fast-track: ${denylistHit ? "denylist override" : "auto-approve"} by CLI session`
4101
+ }
4102
+ }
4103
+ );
4104
+ } catch (err) {
4105
+ spinner?.fail(
4106
+ `#${ticket.sequence_number} fast-approve failed: ${err.message.slice(0, 120)}`
4107
+ );
4108
+ throw err;
4109
+ }
4110
+ if (spinner) {
4111
+ spinner.text = `#${ticket.sequence_number} building`;
4112
+ }
4113
+ const innerOpts = { ...innerWorkOpts };
4114
+ if (scheduleId !== void 0) innerOpts.scheduleId = scheduleId;
4115
+ try {
4116
+ const outcome = await processOneTicket(ctx, innerOpts, ticket.ticket_id);
4117
+ if (outcome.kind === "no_eligible") {
4118
+ spinner?.warn(`#${ticket.sequence_number} unexpectedly invisible to work after approve`);
4119
+ return {
4120
+ kind: "processed",
4121
+ result: {
4122
+ sequenceNumber: ticket.sequence_number,
4123
+ ticketId: ticket.ticket_id,
4124
+ previousFixStatus: approved.from_status ?? "unknown",
4125
+ status: "failed",
4126
+ error: "ticket vanished between approve and claim"
4127
+ }
4128
+ };
4129
+ }
4130
+ if (outcome.kind === "completed") {
4131
+ spinner?.succeed(
4132
+ `#${ticket.sequence_number} PR opened${outcome.prUrl ? ` ${outcome.prUrl}` : ""}`
4133
+ );
4134
+ const result = {
4135
+ sequenceNumber: outcome.sequenceNumber,
4136
+ ticketId: ticket.ticket_id,
4137
+ previousFixStatus: approved.from_status ?? "unknown",
4138
+ status: "completed"
4139
+ };
4140
+ if (outcome.prNumber !== void 0) result.prNumber = outcome.prNumber;
4141
+ if (outcome.prUrl !== void 0) result.prUrl = outcome.prUrl;
4142
+ return { kind: "processed", result };
4143
+ }
4144
+ if (outcome.kind === "dry_run") {
4145
+ spinner?.succeed(`#${ticket.sequence_number} dry-run (no PR)`);
4146
+ return {
4147
+ kind: "processed",
4148
+ result: {
4149
+ sequenceNumber: outcome.sequenceNumber,
4150
+ ticketId: ticket.ticket_id,
4151
+ previousFixStatus: approved.from_status ?? "unknown",
4152
+ status: "dry_run"
4153
+ }
4154
+ };
4155
+ }
4156
+ spinner?.warn(`#${ticket.sequence_number} agent produced no changes`);
4157
+ return {
4158
+ kind: "processed",
4159
+ result: {
4160
+ sequenceNumber: outcome.sequenceNumber,
4161
+ ticketId: ticket.ticket_id,
4162
+ previousFixStatus: approved.from_status ?? "unknown",
4163
+ status: "no_changes"
4164
+ }
4165
+ };
4166
+ } catch (err) {
4167
+ spinner?.fail(
4168
+ `#${ticket.sequence_number} work failed: ${err.message.slice(0, 120)}`
4169
+ );
4170
+ throw err;
4171
+ }
4172
+ }
4173
+ function summarise(results) {
4174
+ if (results.length === 0) return;
4175
+ const counts = results.reduce(
4176
+ (acc, r) => {
4177
+ acc[r.status] = (acc[r.status] ?? 0) + 1;
4178
+ return acc;
4179
+ },
4180
+ {}
4181
+ );
4182
+ const parts = [];
4183
+ if (counts.completed) parts.push(`${c.ok(String(counts.completed))} PR(s) opened`);
4184
+ if (counts.dry_run) parts.push(`${c.dim(String(counts.dry_run))} dry-run`);
4185
+ if (counts.no_changes) parts.push(`${c.dim(String(counts.no_changes))} no-changes`);
4186
+ if (counts.scan_skipped) parts.push(`${c.warn(String(counts.scan_skipped))} scan skipped`);
4187
+ if (counts.failed) parts.push(`${c.err(String(counts.failed))} failed`);
4188
+ process.stdout.write(`
4189
+ ${c.bold("Fast-track summary")}: ${parts.join(", ")}
4190
+ `);
4191
+ const denylistOverrides = results.filter((r) => r.previousFixStatus === "needs_review").length;
4192
+ if (denylistOverrides > 0) {
4193
+ process.stdout.write(
4194
+ `${c.warn("\u26A0")} ${denylistOverrides} ticket(s) bypassed the denylist gate \u2014 review the PR diff(s) carefully.
4195
+ `
4196
+ );
4197
+ }
4198
+ }
4199
+
3763
4200
  // src/commands/pr-test.ts
3764
4201
  import { execFileSync as execFileSync8 } from "child_process";
3765
4202
  function registerPrTest(program2) {
@@ -3898,16 +4335,16 @@ ${c.err("\u2717 pr-test failed")}: ${err.message}
3898
4335
  }
3899
4336
 
3900
4337
  // src/commands/scheduled-task.ts
3901
- import { randomUUID as randomUUID3 } from "crypto";
4338
+ import { randomUUID as randomUUID4 } from "crypto";
3902
4339
 
3903
4340
  // src/scheduler/index.ts
3904
4341
  import { platform as platform2 } from "os";
3905
4342
 
3906
4343
  // src/scheduler/launchd.ts
3907
- import { mkdir as mkdir8, readFile as readFile5, writeFile as writeFile9, unlink as unlink4, readdir as readdir2 } from "fs/promises";
4344
+ import { mkdir as mkdir9, readFile as readFile5, writeFile as writeFile10, unlink as unlink4, readdir as readdir2 } from "fs/promises";
3908
4345
  import { homedir as homedir6 } from "os";
3909
- import { join as join9 } from "path";
3910
- import { execFileSync as execFileSync9, spawn as spawn4 } from "child_process";
4346
+ import { join as join11 } from "path";
4347
+ import { execFileSync as execFileSync9, spawn as spawn5 } from "child_process";
3911
4348
 
3912
4349
  // src/scheduler/cron-translate.ts
3913
4350
  function translateToLaunchd(cron) {
@@ -4008,14 +4445,14 @@ function expandField(field, min, max) {
4008
4445
  }
4009
4446
 
4010
4447
  // src/scheduler/launchd.ts
4011
- var PLIST_DIR = join9(homedir6(), "Library", "LaunchAgents");
4448
+ var PLIST_DIR = join11(homedir6(), "Library", "LaunchAgents");
4012
4449
  var LABEL_PREFIX = "com.inteeka.task.cli.";
4013
4450
  var SAFE_ID_RE = /^[0-9a-zA-Z._-]+$/;
4014
4451
  function plistPath(id) {
4015
4452
  if (!SAFE_ID_RE.test(id) || id.includes("..")) {
4016
4453
  throw new Error(`Refusing to compute plist path for unsafe id: ${id}`);
4017
4454
  }
4018
- return join9(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
4455
+ return join11(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
4019
4456
  }
4020
4457
  function buildPlist(entry) {
4021
4458
  const calendars = translateToLaunchd(entry.cron);
@@ -4051,9 +4488,9 @@ ${fields}
4051
4488
  ` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
4052
4489
  ` </dict>`,
4053
4490
  ` <key>StandardOutPath</key>`,
4054
- ` <string>${escapeXml(join9(homedir6(), ".cache", "task", "launchd-stdout.log"))}</string>`,
4491
+ ` <string>${escapeXml(join11(homedir6(), ".cache", "task", "launchd-stdout.log"))}</string>`,
4055
4492
  ` <key>StandardErrorPath</key>`,
4056
- ` <string>${escapeXml(join9(homedir6(), ".cache", "task", "launchd-stderr.log"))}</string>`,
4493
+ ` <string>${escapeXml(join11(homedir6(), ".cache", "task", "launchd-stderr.log"))}</string>`,
4057
4494
  !entry.enabled ? ` <key>Disabled</key>
4058
4495
  <true/>` : "",
4059
4496
  "</dict>",
@@ -4071,9 +4508,9 @@ function bootstrapDomain() {
4071
4508
  }
4072
4509
  var launchdAdapter = {
4073
4510
  async upsert(entry) {
4074
- await mkdir8(PLIST_DIR, { recursive: true });
4511
+ await mkdir9(PLIST_DIR, { recursive: true });
4075
4512
  const path = plistPath(entry.id);
4076
- await writeFile9(path, buildPlist(entry));
4513
+ await writeFile10(path, buildPlist(entry));
4077
4514
  try {
4078
4515
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
4079
4516
  } catch {
@@ -4102,7 +4539,7 @@ var launchdAdapter = {
4102
4539
  for (const file of ours) {
4103
4540
  const id = file.slice(LABEL_PREFIX.length, -".plist".length);
4104
4541
  try {
4105
- const xml = await readFile5(join9(PLIST_DIR, file), "utf8");
4542
+ const xml = await readFile5(join11(PLIST_DIR, file), "utf8");
4106
4543
  const cron = xml.match(/<key>StartCalendarInterval<\/key>[\s\S]*?<\/array>/)?.[0] ?? "";
4107
4544
  const command = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
4108
4545
  const disabled = /<key>Disabled<\/key>\s*<true\/>/.test(xml);
@@ -4125,7 +4562,7 @@ var launchdAdapter = {
4125
4562
  return new Promise((resolve2) => {
4126
4563
  const args = entry.command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [entry.command];
4127
4564
  const cmd = args.shift() ?? entry.command;
4128
- const child = spawn4(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
4565
+ const child = spawn5(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
4129
4566
  let stdoutTail = "";
4130
4567
  let stderrTail = "";
4131
4568
  child.stdout?.on("data", (chunk) => {
@@ -4148,7 +4585,7 @@ var launchdAdapter = {
4148
4585
  }
4149
4586
  if (enabled) {
4150
4587
  xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
4151
- await writeFile9(path, xml);
4588
+ await writeFile10(path, xml);
4152
4589
  try {
4153
4590
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
4154
4591
  } catch {
@@ -4160,7 +4597,7 @@ var launchdAdapter = {
4160
4597
  "</dict>\n</plist>",
4161
4598
  " <key>Disabled</key>\n <true/>\n</dict>\n</plist>"
4162
4599
  );
4163
- await writeFile9(path, xml);
4600
+ await writeFile10(path, xml);
4164
4601
  }
4165
4602
  try {
4166
4603
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
@@ -4171,7 +4608,7 @@ var launchdAdapter = {
4171
4608
  };
4172
4609
 
4173
4610
  // src/scheduler/cron.ts
4174
- import { execFileSync as execFileSync10, spawn as spawn5 } from "child_process";
4611
+ import { execFileSync as execFileSync10, spawn as spawn6 } from "child_process";
4175
4612
 
4176
4613
  // src/scheduler/safe-command.ts
4177
4614
  var FORBIDDEN = /[;&|`$()<>\\]/;
@@ -4232,7 +4669,7 @@ function readCrontab() {
4232
4669
  }
4233
4670
  }
4234
4671
  function writeCrontab(text) {
4235
- const child = spawn5("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
4672
+ const child = spawn6("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
4236
4673
  child.stdin.write(text);
4237
4674
  child.stdin.end();
4238
4675
  }
@@ -4313,7 +4750,7 @@ var cronAdapter = {
4313
4750
  return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
4314
4751
  }
4315
4752
  return new Promise((resolve2) => {
4316
- const child = spawn5(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4753
+ const child = spawn6(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4317
4754
  let stdoutTail = "";
4318
4755
  let stderrTail = "";
4319
4756
  child.stdout?.on(
@@ -4341,7 +4778,7 @@ var cronAdapter = {
4341
4778
  };
4342
4779
 
4343
4780
  // src/scheduler/windows.ts
4344
- import { execFileSync as execFileSync11, spawn as spawn6 } from "child_process";
4781
+ import { execFileSync as execFileSync11, spawn as spawn7 } from "child_process";
4345
4782
  var TASK_PREFIX = "TaskCLI_";
4346
4783
  function taskName(id) {
4347
4784
  return `${TASK_PREFIX}${id.replace(/[^A-Za-z0-9_-]/g, "_")}`;
@@ -4454,7 +4891,7 @@ var windowsAdapter = {
4454
4891
  return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
4455
4892
  }
4456
4893
  return new Promise((resolve2) => {
4457
- const child = spawn6(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4894
+ const child = spawn7(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4458
4895
  let stdoutTail = "";
4459
4896
  let stderrTail = "";
4460
4897
  child.stdout?.on(
@@ -4513,10 +4950,10 @@ var unsupportedAdapter = {
4513
4950
  };
4514
4951
 
4515
4952
  // src/scheduler/registry.ts
4516
- import { mkdir as mkdir9, readFile as readFile6, writeFile as writeFile10 } from "fs/promises";
4953
+ import { mkdir as mkdir10, readFile as readFile6, writeFile as writeFile11 } from "fs/promises";
4517
4954
  import { homedir as homedir7 } from "os";
4518
- import { dirname as dirname4, join as join10 } from "path";
4519
- var REGISTRY_PATH = join10(homedir7(), ".config", "task", "schedules.json");
4955
+ import { dirname as dirname5, join as join12 } from "path";
4956
+ var REGISTRY_PATH = join12(homedir7(), ".config", "task", "schedules.json");
4520
4957
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4521
4958
  function looksLikeRegistryRow(value) {
4522
4959
  if (!value || typeof value !== "object") return false;
@@ -4536,8 +4973,8 @@ async function readRegistry() {
4536
4973
  }
4537
4974
  }
4538
4975
  async function writeRegistry(rows) {
4539
- await mkdir9(dirname4(REGISTRY_PATH), { recursive: true });
4540
- await writeFile10(REGISTRY_PATH, JSON.stringify(rows, null, 2));
4976
+ await mkdir10(dirname5(REGISTRY_PATH), { recursive: true });
4977
+ await writeFile11(REGISTRY_PATH, JSON.stringify(rows, null, 2));
4541
4978
  }
4542
4979
  async function upsertRegistry(row) {
4543
4980
  if (!UUID_RE.test(row.id)) {
@@ -4625,7 +5062,7 @@ function registerScheduledTask(program2) {
4625
5062
  const max = Math.min(100, Math.max(1, parseInt(opts.max, 10) || 5));
4626
5063
  const command = opts.command ?? `task work --auto --silent --max ${max}`;
4627
5064
  const { hostId, hostLabel } = getHostInfo();
4628
- const id = randomUUID3();
5065
+ const id = randomUUID4();
4629
5066
  const created = await apiCall("POST", "/api/v1/cli/schedules", {
4630
5067
  body: {
4631
5068
  name,
@@ -4778,7 +5215,7 @@ function stripAnsi(s) {
4778
5215
  // src/commands/runs.ts
4779
5216
  import { readFile as readFile7 } from "fs/promises";
4780
5217
  import { homedir as homedir8 } from "os";
4781
- import { join as join11 } from "path";
5218
+ import { join as join13 } from "path";
4782
5219
  function registerRuns(program2) {
4783
5220
  const cmd = program2.command("runs").description("Inspect agentic CLI run history");
4784
5221
  cmd.command("list").description("List recent runs").option("--limit <n>", "Max rows", "50").option("--ticket <id>", "Filter by ticket").option("--schedule <id>", "Filter by schedule").action(async (opts) => {
@@ -4807,7 +5244,7 @@ function registerRuns(program2) {
4807
5244
  process.stdout.write(JSON.stringify(row, null, 2) + "\n");
4808
5245
  });
4809
5246
  cmd.command("logs <id>").description("Show captured agent output for a run, if available").action(async (id) => {
4810
- const localPath = join11(homedir8(), ".cache", "task", "runs", `${id}.log`);
5247
+ const localPath = join13(homedir8(), ".cache", "task", "runs", `${id}.log`);
4811
5248
  try {
4812
5249
  const text = await readFile7(localPath, "utf8");
4813
5250
  process.stdout.write(text);
@@ -4880,8 +5317,8 @@ function registerConfig(program2) {
4880
5317
 
4881
5318
  // src/commands/doctor.ts
4882
5319
  import { execFileSync as execFileSync12 } from "child_process";
4883
- import { readFile as readFile8, writeFile as writeFile11 } from "fs/promises";
4884
- import { join as join12 } from "path";
5320
+ import { readFile as readFile8, writeFile as writeFile12 } from "fs/promises";
5321
+ import { join as join14 } from "path";
4885
5322
  import { request as request5 } from "undici";
4886
5323
  var ALLOWED_TEST_EXECUTABLES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun", "node", "npx"]);
4887
5324
  var DEFAULT_TEST_COMMAND = "pnpm typecheck";
@@ -5095,7 +5532,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
5095
5532
  detail: `${command} (non-script executable, not statically verifiable)`
5096
5533
  };
5097
5534
  }
5098
- const pkgPath = join12(root, "package.json");
5535
+ const pkgPath = join14(root, "package.json");
5099
5536
  let pkgRaw;
5100
5537
  try {
5101
5538
  pkgRaw = await readFile8(pkgPath, "utf8");
@@ -5131,7 +5568,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
5131
5568
  pkg.scripts = { ...scripts, typecheck: "tsc --noEmit" };
5132
5569
  const indent = detectIndent(pkgRaw);
5133
5570
  const trailingNewline = pkgRaw.endsWith("\n") ? "\n" : "";
5134
- await writeFile11(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
5571
+ await writeFile12(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
5135
5572
  return {
5136
5573
  name: "pre-push test",
5137
5574
  ok: true,
@@ -5180,7 +5617,7 @@ function checkBinary(name, command) {
5180
5617
  }
5181
5618
 
5182
5619
  // src/commands/version.ts
5183
- var CLI_VERSION = true ? "0.2.17" : "0.0.0-dev";
5620
+ var CLI_VERSION = true ? "0.2.19" : "0.0.0-dev";
5184
5621
  function registerVersion(program2) {
5185
5622
  program2.command("version").description("Print the CLI version").action(() => {
5186
5623
  process.stdout.write(CLI_VERSION + "\n");
@@ -5207,6 +5644,7 @@ registerMultiWork(program);
5207
5644
  registerResume(program);
5208
5645
  registerReset(program);
5209
5646
  registerScan(program);
5647
+ registerFastTrack(program);
5210
5648
  registerPrTest(program);
5211
5649
  registerScheduledTask(program);
5212
5650
  registerRuns(program);