@inteeka/task-cli 0.2.17 → 0.2.18

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
  );
@@ -3760,6 +3948,252 @@ function clampInt(raw, min, max, fallback) {
3760
3948
  return Math.min(v, max);
3761
3949
  }
3762
3950
 
3951
+ // src/commands/fast-track.ts
3952
+ import { randomUUID as randomUUID3 } from "crypto";
3953
+ import ora3 from "ora";
3954
+ function registerFastTrack(program2) {
3955
+ program2.command("fast-track").description(
3956
+ "End-to-end: scan + auto-approve + work on the next CLI-eligible ticket(s) in the linked project \u2014 no admin review step"
3957
+ ).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(
3958
+ "--reset",
3959
+ "DESTRUCTIVE: discard local working-tree changes before the first ticket. Requires --confirm in non-TTY contexts."
3960
+ ).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) => {
3961
+ await runFastTrack(opts);
3962
+ });
3963
+ }
3964
+ async function runFastTrack(opts) {
3965
+ let creds = await readCredentials();
3966
+ if (!creds) {
3967
+ throw new CliError(
3968
+ CLI_EXIT_CODES.MISCONFIGURATION,
3969
+ "Not signed in",
3970
+ "Run 'task login' to authenticate."
3971
+ );
3972
+ }
3973
+ creds = await ensureFreshAccessToken(creds);
3974
+ const localCfg = await readLocalConfig();
3975
+ const linkedProject = await readProjectConfig(findRepoRoot());
3976
+ if (!linkedProject) {
3977
+ throw new CliError(
3978
+ CLI_EXIT_CODES.MISCONFIGURATION,
3979
+ "No project link in this repo",
3980
+ "Run 'task link' first \u2014 fast-track operates on the linked project only."
3981
+ );
3982
+ }
3983
+ const apiUrl = (opts.apiUrl ?? process.env["TASK_API_URL"] ?? creds.api_url ?? localCfg.api_url ?? linkedProject.api_url ?? "http://localhost:3400").replace(/\/$/, "");
3984
+ const max = Math.max(1, parseInt(opts.max, 10) || 1);
3985
+ const silent = !!opts.silent || localCfg.silent;
3986
+ const ctx = await buildWorkContext({ max: "1", silent: opts.silent });
3987
+ const api = new AutopilotApi({ apiUrl, creds });
3988
+ const claudePath = localCfg.claude_path ?? void 0;
3989
+ let firstIteration = true;
3990
+ const results = [];
3991
+ for (let i = 0; i < max; i++) {
3992
+ const innerWorkOpts = {
3993
+ auto: false,
3994
+ // We pin a specific ticketId per iteration.
3995
+ next: false,
3996
+ max: "1",
3997
+ silent: opts.silent,
3998
+ ...opts.dryRun ? { dryRun: true } : {},
3999
+ ...opts.scheduleId ? { scheduleId: opts.scheduleId } : {},
4000
+ ...firstIteration && opts.reset ? { reset: true } : {},
4001
+ ...firstIteration && opts.confirm ? { confirm: true } : {}
4002
+ };
4003
+ const outcome = await fastTrackOneTicket({
4004
+ api,
4005
+ apiUrl,
4006
+ project: linkedProject,
4007
+ ctx,
4008
+ claudePath,
4009
+ silent,
4010
+ innerWorkOpts,
4011
+ scheduleId: opts.scheduleId
4012
+ });
4013
+ if (outcome.kind === "no_eligible") {
4014
+ if (results.length === 0 && !silent) {
4015
+ process.stdout.write(c.dim("No CLI-eligible tickets available to fast-track.\n"));
4016
+ }
4017
+ break;
4018
+ }
4019
+ results.push(outcome.result);
4020
+ firstIteration = false;
4021
+ if (i < max - 1 && outcome.result.status === "completed") {
4022
+ enforceBaseBranchClean(ctx.cwd, ctx.baseBranch);
4023
+ }
4024
+ }
4025
+ if (!silent) {
4026
+ summarise(results);
4027
+ }
4028
+ }
4029
+ async function fastTrackOneTicket(args) {
4030
+ const { api, project, ctx, claudePath, silent, innerWorkOpts, scheduleId } = args;
4031
+ const issued = await api.issueSkillToken({
4032
+ project_id: project.project_id,
4033
+ max_submits: 1
4034
+ });
4035
+ const skillToken = issued.token;
4036
+ const prepared = await api.prepare(skillToken, 1, randomUUID3());
4037
+ if (prepared.tickets.length === 0) {
4038
+ return { kind: "no_eligible" };
4039
+ }
4040
+ const ticket = prepared.tickets[0];
4041
+ const nonce = prepared.prepare_nonce;
4042
+ const spinner = silent ? null : ora3(`#${ticket.sequence_number} ${ticket.title.slice(0, 60)} \u2014 scanning`).start();
4043
+ let generated;
4044
+ try {
4045
+ generated = await generateFixPromptJson({
4046
+ systemPrompt: ticket.system_prompt,
4047
+ repoOverviewBlock: ticket.repo_overview_block,
4048
+ ticketBlock: ticket.ticket_block,
4049
+ outputSchemaHint: ticket.output_schema_hint,
4050
+ modelId: ticket.model_id,
4051
+ ticketId: ticket.ticket_id,
4052
+ ...claudePath ? { claudePath } : {}
4053
+ });
4054
+ } catch (err) {
4055
+ await api.abort(skillToken, [ticket.ticket_id]).catch(() => void 0);
4056
+ const reason = err instanceof LlmGenerationError ? `${err.reason}: ${err.message.slice(0, 200)}` : "";
4057
+ spinner?.fail(`#${ticket.sequence_number} scan failed${reason ? ` (${reason})` : ""}`);
4058
+ throw err instanceof CliError ? err : new CliError(
4059
+ CLI_EXIT_CODES.GENERIC_ERROR,
4060
+ `Fix-prompt generation failed for #${ticket.sequence_number}: ${err.message.slice(0, 200)}`
4061
+ );
4062
+ }
4063
+ const submitted = await api.submit({
4064
+ skillToken,
4065
+ nonce,
4066
+ ticketId: ticket.ticket_id,
4067
+ structured: generated.structured,
4068
+ inputTokens: generated.inputTokens,
4069
+ outputTokens: generated.outputTokens,
4070
+ model: ticket.model_id
4071
+ });
4072
+ if (submitted.status === "skip") {
4073
+ spinner?.warn(`#${ticket.sequence_number} server rejected submission (${submitted.reason})`);
4074
+ return {
4075
+ kind: "processed",
4076
+ result: {
4077
+ sequenceNumber: ticket.sequence_number,
4078
+ ticketId: ticket.ticket_id,
4079
+ previousFixStatus: submitted.reason,
4080
+ status: "scan_skipped",
4081
+ error: submitted.reason
4082
+ }
4083
+ };
4084
+ }
4085
+ const denylistHit = submitted.status === "needs_review";
4086
+ if (spinner) {
4087
+ spinner.text = `#${ticket.sequence_number} approving${denylistHit ? " (denylist override)" : ""}`;
4088
+ }
4089
+ let approved;
4090
+ try {
4091
+ approved = await apiCallOrThrow(
4092
+ "POST",
4093
+ `/api/v1/cli/me/tickets/${ticket.ticket_id}/ai-fix-prompt/fast-approve`,
4094
+ {
4095
+ body: {
4096
+ ...denylistHit ? { denylist_acknowledged: true } : {},
4097
+ reason: `fast-track: ${denylistHit ? "denylist override" : "auto-approve"} by CLI session`
4098
+ }
4099
+ }
4100
+ );
4101
+ } catch (err) {
4102
+ spinner?.fail(
4103
+ `#${ticket.sequence_number} fast-approve failed: ${err.message.slice(0, 120)}`
4104
+ );
4105
+ throw err;
4106
+ }
4107
+ if (spinner) {
4108
+ spinner.text = `#${ticket.sequence_number} building`;
4109
+ }
4110
+ const innerOpts = { ...innerWorkOpts };
4111
+ if (scheduleId !== void 0) innerOpts.scheduleId = scheduleId;
4112
+ try {
4113
+ const outcome = await processOneTicket(ctx, innerOpts, ticket.ticket_id);
4114
+ if (outcome.kind === "no_eligible") {
4115
+ spinner?.warn(`#${ticket.sequence_number} unexpectedly invisible to work after approve`);
4116
+ return {
4117
+ kind: "processed",
4118
+ result: {
4119
+ sequenceNumber: ticket.sequence_number,
4120
+ ticketId: ticket.ticket_id,
4121
+ previousFixStatus: approved.from_status ?? "unknown",
4122
+ status: "failed",
4123
+ error: "ticket vanished between approve and claim"
4124
+ }
4125
+ };
4126
+ }
4127
+ if (outcome.kind === "completed") {
4128
+ spinner?.succeed(
4129
+ `#${ticket.sequence_number} PR opened${outcome.prUrl ? ` ${outcome.prUrl}` : ""}`
4130
+ );
4131
+ const result = {
4132
+ sequenceNumber: outcome.sequenceNumber,
4133
+ ticketId: ticket.ticket_id,
4134
+ previousFixStatus: approved.from_status ?? "unknown",
4135
+ status: "completed"
4136
+ };
4137
+ if (outcome.prNumber !== void 0) result.prNumber = outcome.prNumber;
4138
+ if (outcome.prUrl !== void 0) result.prUrl = outcome.prUrl;
4139
+ return { kind: "processed", result };
4140
+ }
4141
+ if (outcome.kind === "dry_run") {
4142
+ spinner?.succeed(`#${ticket.sequence_number} dry-run (no PR)`);
4143
+ return {
4144
+ kind: "processed",
4145
+ result: {
4146
+ sequenceNumber: outcome.sequenceNumber,
4147
+ ticketId: ticket.ticket_id,
4148
+ previousFixStatus: approved.from_status ?? "unknown",
4149
+ status: "dry_run"
4150
+ }
4151
+ };
4152
+ }
4153
+ spinner?.warn(`#${ticket.sequence_number} agent produced no changes`);
4154
+ return {
4155
+ kind: "processed",
4156
+ result: {
4157
+ sequenceNumber: outcome.sequenceNumber,
4158
+ ticketId: ticket.ticket_id,
4159
+ previousFixStatus: approved.from_status ?? "unknown",
4160
+ status: "no_changes"
4161
+ }
4162
+ };
4163
+ } catch (err) {
4164
+ spinner?.fail(
4165
+ `#${ticket.sequence_number} work failed: ${err.message.slice(0, 120)}`
4166
+ );
4167
+ throw err;
4168
+ }
4169
+ }
4170
+ function summarise(results) {
4171
+ if (results.length === 0) return;
4172
+ const counts = results.reduce(
4173
+ (acc, r) => {
4174
+ acc[r.status] = (acc[r.status] ?? 0) + 1;
4175
+ return acc;
4176
+ },
4177
+ {}
4178
+ );
4179
+ const parts = [];
4180
+ if (counts.completed) parts.push(`${c.ok(String(counts.completed))} PR(s) opened`);
4181
+ if (counts.dry_run) parts.push(`${c.dim(String(counts.dry_run))} dry-run`);
4182
+ if (counts.no_changes) parts.push(`${c.dim(String(counts.no_changes))} no-changes`);
4183
+ if (counts.scan_skipped) parts.push(`${c.warn(String(counts.scan_skipped))} scan skipped`);
4184
+ if (counts.failed) parts.push(`${c.err(String(counts.failed))} failed`);
4185
+ process.stdout.write(`
4186
+ ${c.bold("Fast-track summary")}: ${parts.join(", ")}
4187
+ `);
4188
+ const denylistOverrides = results.filter((r) => r.previousFixStatus === "needs_review").length;
4189
+ if (denylistOverrides > 0) {
4190
+ process.stdout.write(
4191
+ `${c.warn("\u26A0")} ${denylistOverrides} ticket(s) bypassed the denylist gate \u2014 review the PR diff(s) carefully.
4192
+ `
4193
+ );
4194
+ }
4195
+ }
4196
+
3763
4197
  // src/commands/pr-test.ts
3764
4198
  import { execFileSync as execFileSync8 } from "child_process";
3765
4199
  function registerPrTest(program2) {
@@ -3898,16 +4332,16 @@ ${c.err("\u2717 pr-test failed")}: ${err.message}
3898
4332
  }
3899
4333
 
3900
4334
  // src/commands/scheduled-task.ts
3901
- import { randomUUID as randomUUID3 } from "crypto";
4335
+ import { randomUUID as randomUUID4 } from "crypto";
3902
4336
 
3903
4337
  // src/scheduler/index.ts
3904
4338
  import { platform as platform2 } from "os";
3905
4339
 
3906
4340
  // src/scheduler/launchd.ts
3907
- import { mkdir as mkdir8, readFile as readFile5, writeFile as writeFile9, unlink as unlink4, readdir as readdir2 } from "fs/promises";
4341
+ import { mkdir as mkdir9, readFile as readFile5, writeFile as writeFile10, unlink as unlink4, readdir as readdir2 } from "fs/promises";
3908
4342
  import { homedir as homedir6 } from "os";
3909
- import { join as join9 } from "path";
3910
- import { execFileSync as execFileSync9, spawn as spawn4 } from "child_process";
4343
+ import { join as join11 } from "path";
4344
+ import { execFileSync as execFileSync9, spawn as spawn5 } from "child_process";
3911
4345
 
3912
4346
  // src/scheduler/cron-translate.ts
3913
4347
  function translateToLaunchd(cron) {
@@ -4008,14 +4442,14 @@ function expandField(field, min, max) {
4008
4442
  }
4009
4443
 
4010
4444
  // src/scheduler/launchd.ts
4011
- var PLIST_DIR = join9(homedir6(), "Library", "LaunchAgents");
4445
+ var PLIST_DIR = join11(homedir6(), "Library", "LaunchAgents");
4012
4446
  var LABEL_PREFIX = "com.inteeka.task.cli.";
4013
4447
  var SAFE_ID_RE = /^[0-9a-zA-Z._-]+$/;
4014
4448
  function plistPath(id) {
4015
4449
  if (!SAFE_ID_RE.test(id) || id.includes("..")) {
4016
4450
  throw new Error(`Refusing to compute plist path for unsafe id: ${id}`);
4017
4451
  }
4018
- return join9(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
4452
+ return join11(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
4019
4453
  }
4020
4454
  function buildPlist(entry) {
4021
4455
  const calendars = translateToLaunchd(entry.cron);
@@ -4051,9 +4485,9 @@ ${fields}
4051
4485
  ` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
4052
4486
  ` </dict>`,
4053
4487
  ` <key>StandardOutPath</key>`,
4054
- ` <string>${escapeXml(join9(homedir6(), ".cache", "task", "launchd-stdout.log"))}</string>`,
4488
+ ` <string>${escapeXml(join11(homedir6(), ".cache", "task", "launchd-stdout.log"))}</string>`,
4055
4489
  ` <key>StandardErrorPath</key>`,
4056
- ` <string>${escapeXml(join9(homedir6(), ".cache", "task", "launchd-stderr.log"))}</string>`,
4490
+ ` <string>${escapeXml(join11(homedir6(), ".cache", "task", "launchd-stderr.log"))}</string>`,
4057
4491
  !entry.enabled ? ` <key>Disabled</key>
4058
4492
  <true/>` : "",
4059
4493
  "</dict>",
@@ -4071,9 +4505,9 @@ function bootstrapDomain() {
4071
4505
  }
4072
4506
  var launchdAdapter = {
4073
4507
  async upsert(entry) {
4074
- await mkdir8(PLIST_DIR, { recursive: true });
4508
+ await mkdir9(PLIST_DIR, { recursive: true });
4075
4509
  const path = plistPath(entry.id);
4076
- await writeFile9(path, buildPlist(entry));
4510
+ await writeFile10(path, buildPlist(entry));
4077
4511
  try {
4078
4512
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
4079
4513
  } catch {
@@ -4102,7 +4536,7 @@ var launchdAdapter = {
4102
4536
  for (const file of ours) {
4103
4537
  const id = file.slice(LABEL_PREFIX.length, -".plist".length);
4104
4538
  try {
4105
- const xml = await readFile5(join9(PLIST_DIR, file), "utf8");
4539
+ const xml = await readFile5(join11(PLIST_DIR, file), "utf8");
4106
4540
  const cron = xml.match(/<key>StartCalendarInterval<\/key>[\s\S]*?<\/array>/)?.[0] ?? "";
4107
4541
  const command = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
4108
4542
  const disabled = /<key>Disabled<\/key>\s*<true\/>/.test(xml);
@@ -4125,7 +4559,7 @@ var launchdAdapter = {
4125
4559
  return new Promise((resolve2) => {
4126
4560
  const args = entry.command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [entry.command];
4127
4561
  const cmd = args.shift() ?? entry.command;
4128
- const child = spawn4(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
4562
+ const child = spawn5(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
4129
4563
  let stdoutTail = "";
4130
4564
  let stderrTail = "";
4131
4565
  child.stdout?.on("data", (chunk) => {
@@ -4148,7 +4582,7 @@ var launchdAdapter = {
4148
4582
  }
4149
4583
  if (enabled) {
4150
4584
  xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
4151
- await writeFile9(path, xml);
4585
+ await writeFile10(path, xml);
4152
4586
  try {
4153
4587
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
4154
4588
  } catch {
@@ -4160,7 +4594,7 @@ var launchdAdapter = {
4160
4594
  "</dict>\n</plist>",
4161
4595
  " <key>Disabled</key>\n <true/>\n</dict>\n</plist>"
4162
4596
  );
4163
- await writeFile9(path, xml);
4597
+ await writeFile10(path, xml);
4164
4598
  }
4165
4599
  try {
4166
4600
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
@@ -4171,7 +4605,7 @@ var launchdAdapter = {
4171
4605
  };
4172
4606
 
4173
4607
  // src/scheduler/cron.ts
4174
- import { execFileSync as execFileSync10, spawn as spawn5 } from "child_process";
4608
+ import { execFileSync as execFileSync10, spawn as spawn6 } from "child_process";
4175
4609
 
4176
4610
  // src/scheduler/safe-command.ts
4177
4611
  var FORBIDDEN = /[;&|`$()<>\\]/;
@@ -4232,7 +4666,7 @@ function readCrontab() {
4232
4666
  }
4233
4667
  }
4234
4668
  function writeCrontab(text) {
4235
- const child = spawn5("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
4669
+ const child = spawn6("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
4236
4670
  child.stdin.write(text);
4237
4671
  child.stdin.end();
4238
4672
  }
@@ -4313,7 +4747,7 @@ var cronAdapter = {
4313
4747
  return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
4314
4748
  }
4315
4749
  return new Promise((resolve2) => {
4316
- const child = spawn5(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4750
+ const child = spawn6(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4317
4751
  let stdoutTail = "";
4318
4752
  let stderrTail = "";
4319
4753
  child.stdout?.on(
@@ -4341,7 +4775,7 @@ var cronAdapter = {
4341
4775
  };
4342
4776
 
4343
4777
  // src/scheduler/windows.ts
4344
- import { execFileSync as execFileSync11, spawn as spawn6 } from "child_process";
4778
+ import { execFileSync as execFileSync11, spawn as spawn7 } from "child_process";
4345
4779
  var TASK_PREFIX = "TaskCLI_";
4346
4780
  function taskName(id) {
4347
4781
  return `${TASK_PREFIX}${id.replace(/[^A-Za-z0-9_-]/g, "_")}`;
@@ -4454,7 +4888,7 @@ var windowsAdapter = {
4454
4888
  return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
4455
4889
  }
4456
4890
  return new Promise((resolve2) => {
4457
- const child = spawn6(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4891
+ const child = spawn7(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
4458
4892
  let stdoutTail = "";
4459
4893
  let stderrTail = "";
4460
4894
  child.stdout?.on(
@@ -4513,10 +4947,10 @@ var unsupportedAdapter = {
4513
4947
  };
4514
4948
 
4515
4949
  // src/scheduler/registry.ts
4516
- import { mkdir as mkdir9, readFile as readFile6, writeFile as writeFile10 } from "fs/promises";
4950
+ import { mkdir as mkdir10, readFile as readFile6, writeFile as writeFile11 } from "fs/promises";
4517
4951
  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");
4952
+ import { dirname as dirname5, join as join12 } from "path";
4953
+ var REGISTRY_PATH = join12(homedir7(), ".config", "task", "schedules.json");
4520
4954
  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
4955
  function looksLikeRegistryRow(value) {
4522
4956
  if (!value || typeof value !== "object") return false;
@@ -4536,8 +4970,8 @@ async function readRegistry() {
4536
4970
  }
4537
4971
  }
4538
4972
  async function writeRegistry(rows) {
4539
- await mkdir9(dirname4(REGISTRY_PATH), { recursive: true });
4540
- await writeFile10(REGISTRY_PATH, JSON.stringify(rows, null, 2));
4973
+ await mkdir10(dirname5(REGISTRY_PATH), { recursive: true });
4974
+ await writeFile11(REGISTRY_PATH, JSON.stringify(rows, null, 2));
4541
4975
  }
4542
4976
  async function upsertRegistry(row) {
4543
4977
  if (!UUID_RE.test(row.id)) {
@@ -4625,7 +5059,7 @@ function registerScheduledTask(program2) {
4625
5059
  const max = Math.min(100, Math.max(1, parseInt(opts.max, 10) || 5));
4626
5060
  const command = opts.command ?? `task work --auto --silent --max ${max}`;
4627
5061
  const { hostId, hostLabel } = getHostInfo();
4628
- const id = randomUUID3();
5062
+ const id = randomUUID4();
4629
5063
  const created = await apiCall("POST", "/api/v1/cli/schedules", {
4630
5064
  body: {
4631
5065
  name,
@@ -4778,7 +5212,7 @@ function stripAnsi(s) {
4778
5212
  // src/commands/runs.ts
4779
5213
  import { readFile as readFile7 } from "fs/promises";
4780
5214
  import { homedir as homedir8 } from "os";
4781
- import { join as join11 } from "path";
5215
+ import { join as join13 } from "path";
4782
5216
  function registerRuns(program2) {
4783
5217
  const cmd = program2.command("runs").description("Inspect agentic CLI run history");
4784
5218
  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 +5241,7 @@ function registerRuns(program2) {
4807
5241
  process.stdout.write(JSON.stringify(row, null, 2) + "\n");
4808
5242
  });
4809
5243
  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`);
5244
+ const localPath = join13(homedir8(), ".cache", "task", "runs", `${id}.log`);
4811
5245
  try {
4812
5246
  const text = await readFile7(localPath, "utf8");
4813
5247
  process.stdout.write(text);
@@ -4880,8 +5314,8 @@ function registerConfig(program2) {
4880
5314
 
4881
5315
  // src/commands/doctor.ts
4882
5316
  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";
5317
+ import { readFile as readFile8, writeFile as writeFile12 } from "fs/promises";
5318
+ import { join as join14 } from "path";
4885
5319
  import { request as request5 } from "undici";
4886
5320
  var ALLOWED_TEST_EXECUTABLES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun", "node", "npx"]);
4887
5321
  var DEFAULT_TEST_COMMAND = "pnpm typecheck";
@@ -5095,7 +5529,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
5095
5529
  detail: `${command} (non-script executable, not statically verifiable)`
5096
5530
  };
5097
5531
  }
5098
- const pkgPath = join12(root, "package.json");
5532
+ const pkgPath = join14(root, "package.json");
5099
5533
  let pkgRaw;
5100
5534
  try {
5101
5535
  pkgRaw = await readFile8(pkgPath, "utf8");
@@ -5131,7 +5565,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
5131
5565
  pkg.scripts = { ...scripts, typecheck: "tsc --noEmit" };
5132
5566
  const indent = detectIndent(pkgRaw);
5133
5567
  const trailingNewline = pkgRaw.endsWith("\n") ? "\n" : "";
5134
- await writeFile11(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
5568
+ await writeFile12(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
5135
5569
  return {
5136
5570
  name: "pre-push test",
5137
5571
  ok: true,
@@ -5180,7 +5614,7 @@ function checkBinary(name, command) {
5180
5614
  }
5181
5615
 
5182
5616
  // src/commands/version.ts
5183
- var CLI_VERSION = true ? "0.2.17" : "0.0.0-dev";
5617
+ var CLI_VERSION = true ? "0.2.18" : "0.0.0-dev";
5184
5618
  function registerVersion(program2) {
5185
5619
  program2.command("version").description("Print the CLI version").action(() => {
5186
5620
  process.stdout.write(CLI_VERSION + "\n");
@@ -5207,6 +5641,7 @@ registerMultiWork(program);
5207
5641
  registerResume(program);
5208
5642
  registerReset(program);
5209
5643
  registerScan(program);
5644
+ registerFastTrack(program);
5210
5645
  registerPrTest(program);
5211
5646
  registerScheduledTask(program);
5212
5647
  registerRuns(program);