@inteeka/task-cli 0.2.26 → 0.2.27

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
@@ -171,6 +171,17 @@ var CLI_ALLOWED_TOOLS = Object.freeze([
171
171
  "Bash(pnpm typecheck*)",
172
172
  "Bash(pnpm lint*)"
173
173
  ]);
174
+ var CLI_REVIEW_ALLOWED_TOOLS = Object.freeze([
175
+ "Read",
176
+ "Glob",
177
+ "Grep",
178
+ "Bash(git diff:*)",
179
+ "Bash(git log:*)",
180
+ "Bash(git show:*)",
181
+ "Bash(git status)",
182
+ "Bash(git branch:*)",
183
+ "Bash(gh pr view:*)"
184
+ ]);
174
185
  var CLI_DEVICE_POLL_INTERVAL_SECONDS = 5;
175
186
  var CLI_DEVICE_POLL_SLOW_DOWN_INCREMENT_SECONDS = 5;
176
187
  var CLI_ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
@@ -193,6 +204,13 @@ var CLI_AUDIT_ACTIONS = Object.freeze([
193
204
  "cli.run.pr_failed",
194
205
  "cli.run.push_failed",
195
206
  "cli.run.resumed",
207
+ "cli.run.auto_review_passed",
208
+ "cli.run.auto_review_failed",
209
+ "cli.run.auto_review_errored",
210
+ // Server-side audit actions for the auto-review verdict path. Emitted
211
+ // by /api/v1/cli/me/tickets/[id]/pull-requests/[prNumber]/auto-review-verdict.
212
+ "git.pr.auto_review_passed",
213
+ "git.pr.auto_review_failed",
196
214
  "cli.work.pr_recovered",
197
215
  "cli.schedule.created",
198
216
  "cli.schedule.paused",
@@ -1523,6 +1541,10 @@ var ALLOWED_TOOLS = CLI_ALLOWED_TOOLS;
1523
1541
  function allowedToolsFlag() {
1524
1542
  return ALLOWED_TOOLS.join(",");
1525
1543
  }
1544
+ var REVIEW_ALLOWED_TOOLS = CLI_REVIEW_ALLOWED_TOOLS;
1545
+ function reviewAllowedToolsFlag() {
1546
+ return REVIEW_ALLOWED_TOOLS.join(",");
1547
+ }
1526
1548
 
1527
1549
  // src/agent/system-prompt.ts
1528
1550
  function buildSystemPrompt(args) {
@@ -1581,7 +1603,7 @@ async function runAgent(args) {
1581
1603
  logHandle = createWriteStream(outputLogPath, { flags: "a" });
1582
1604
  }
1583
1605
  let stderrBuffer = "";
1584
- const STDERR_KEEP = 4e3;
1606
+ const STDERR_KEEP2 = 4e3;
1585
1607
  return new Promise((resolve2, reject) => {
1586
1608
  const child = spawn(claude, cliArgs, {
1587
1609
  cwd: args.cwd,
@@ -1605,7 +1627,7 @@ async function runAgent(args) {
1605
1627
  } else {
1606
1628
  process.stderr.write(chunk);
1607
1629
  }
1608
- stderrBuffer = (stderrBuffer + chunk.toString("utf8")).slice(-STDERR_KEEP);
1630
+ stderrBuffer = (stderrBuffer + chunk.toString("utf8")).slice(-STDERR_KEEP2);
1609
1631
  });
1610
1632
  child.on("close", (code) => {
1611
1633
  logHandle?.end();
@@ -1615,6 +1637,297 @@ async function runAgent(args) {
1615
1637
  });
1616
1638
  }
1617
1639
 
1640
+ // src/agent/pr-review-service.ts
1641
+ import { spawn as spawn2 } from "child_process";
1642
+ import { mkdir as mkdir7, writeFile as writeFile8 } from "fs/promises";
1643
+ import { homedir as homedir5 } from "os";
1644
+ import { join as join8 } from "path";
1645
+
1646
+ // src/agent/pr-review-prompt.ts
1647
+ var QA_BLOCK = `# /qa lens \u2014 final quality gate
1648
+
1649
+ You are acting as the QA lead reviewer. Score the diff against these checks:
1650
+
1651
+ 1. Contract integrity \u2014 does the change honour the public API / UI contract that callers depend on? Any breaking change without a migration is an automatic NO-GO.
1652
+ 2. Multi-tenant safety \u2014 every query touching tenant data must filter by organisation_id. Any new query missing this filter is a NO-GO.
1653
+ 3. Validation \u2014 every new mutation endpoint has a Zod schema with .strict() on updates, max-lengths on free text, UUID validation on path params. Missing \u2192 NO-GO.
1654
+ 4. Soft delete \u2014 never hard-delete user-facing entities. Hard delete \u2192 NO-GO.
1655
+ 5. UI accessibility \u2014 new interactive elements have semantic markup (button, role, aria-label as needed). Raw <div onClick> on a logical control \u2192 NO-GO.
1656
+ 6. Test coverage \u2014 meaningful new logic should have a test in the same PR. Untested critical path \u2192 CONDITIONAL (not NO-GO unless the path is a security boundary).
1657
+
1658
+ QA verdict scale:
1659
+ GO \u2014 all checks pass, ready to merge
1660
+ CONDITIONAL \u2014 non-blocking gaps (e.g. missing test, small a11y nit). Merge is acceptable but findings should be filed.
1661
+ NO-GO \u2014 at least one blocking check failed
1662
+ `;
1663
+ var SECURITY_BLOCK = `# /security lens \u2014 Security Review Gate
1664
+
1665
+ You are acting as the security agent. Apply the 5 Non-Negotiables and the 5 Security Layers.
1666
+
1667
+ 5 Non-Negotiables (any failure \u2192 FAIL):
1668
+ 1. RLS / organisation_id \u2014 every tenant table query filters by organisation_id; every insert sets it. No bypass via service-role client unless the file is a documented background job.
1669
+ 2. Zod validation \u2014 every mutation endpoint validates inputs; update schemas use .strict().
1670
+ 3. Secrets \u2014 no secrets in code, no NEXT_PUBLIC_ leakage of server-only env vars, no console.log of tokens / cookies / session ids.
1671
+ 4. Audit chain \u2014 mutations on audited resources call writeAuditLog; system_audit_log and ticket_activity_log are never UPDATEd or DELETEd.
1672
+ 5. CSRF / auth \u2014 dashboard mutations require session auth; widget endpoints require API key; webhook endpoints verify HMAC signature.
1673
+
1674
+ 5 Security Layers to review independently:
1675
+ - authentication (session vs api_key vs HMAC selected correctly?)
1676
+ - tenant_resolution (organisation_id resolved from membership / api_key \u2192 project, never from request body?)
1677
+ - authorisation (role check matches endpoint sensitivity?)
1678
+ - rls (queries filtered by organisation_id?)
1679
+ - audit (mutations recorded with writeAuditLog?)
1680
+
1681
+ Security verdict scale:
1682
+ PASS \u2014 all five layers pass, no findings above 'low'
1683
+ PASS_WITH_CONDITIONS \u2014 only 'low' findings; no critical/high/medium issues
1684
+ FAIL \u2014 at least one critical, high, or medium finding, OR any 'fail' in the five layers
1685
+ `;
1686
+ var OUTPUT_CONTRACT = `# Output contract \u2014 REQUIRED
1687
+
1688
+ After your analysis, end your reply with exactly ONE fenced \`\`\`json block matching this schema. No prose after the closing fence.
1689
+
1690
+ \`\`\`json
1691
+ {
1692
+ "overall": "pass" | "fail",
1693
+ "summary": "<one sentence \u2014 what the PR does and the headline verdict>",
1694
+ "qa": {
1695
+ "verdict": "GO" | "CONDITIONAL" | "NO-GO",
1696
+ "failed_checks": ["<short label>", ...]
1697
+ },
1698
+ "security": {
1699
+ "verdict": "PASS" | "PASS_WITH_CONDITIONS" | "FAIL",
1700
+ "layers_reviewed": {
1701
+ "authentication": "PASS" | "FAIL",
1702
+ "tenant_resolution": "PASS" | "FAIL",
1703
+ "authorisation": "PASS" | "FAIL",
1704
+ "rls": "PASS" | "FAIL",
1705
+ "audit": "PASS" | "FAIL"
1706
+ }
1707
+ },
1708
+ "findings": [
1709
+ {
1710
+ "lens": "qa" | "security",
1711
+ "severity": "critical" | "high" | "medium" | "low",
1712
+ "file": "<repo-relative path or null>",
1713
+ "description": "<what is wrong>",
1714
+ "recommendation": "<what to change>"
1715
+ }
1716
+ ]
1717
+ }
1718
+ \`\`\`
1719
+
1720
+ Rules:
1721
+ - "overall": "pass" ONLY when qa.verdict === "GO" AND security.verdict === "PASS". Anything else \u2192 "fail".
1722
+ - "findings" is an array (may be empty on a clean pass).
1723
+ - Keep total response under 4000 tokens \u2014 be concise and actionable.
1724
+ `;
1725
+ function buildPrReviewSystemPrompt(_args) {
1726
+ return [
1727
+ "You are a paranoid senior reviewer auditing a pull request that was generated by an AI agent. Treat every line of the diff as if it were written by an attacker who knows your blind spots.",
1728
+ "",
1729
+ "You have read-only tools (git diff, git log, Read, Grep, Glob, gh pr view). Do NOT attempt to edit or write files; the toolset will refuse.",
1730
+ "",
1731
+ "Two lenses, applied independently then combined. Be evidence-driven \u2014 cite file paths in findings.",
1732
+ "",
1733
+ QA_BLOCK,
1734
+ "",
1735
+ SECURITY_BLOCK,
1736
+ "",
1737
+ OUTPUT_CONTRACT
1738
+ ].join("\n");
1739
+ }
1740
+ function buildPrReviewUserPrompt(args) {
1741
+ return [
1742
+ `PR #${args.prNumber} (${args.prUrl})`,
1743
+ `Branch: ${args.branchName} \u2192 ${args.baseBranch}`,
1744
+ "",
1745
+ `Run \`git diff ${args.baseBranch}...${args.branchName}\` to see every changed line. If the diff is large, also run \`git diff ${args.baseBranch}...${args.branchName} --stat\` first to orient yourself, then drill into files that look risky.`,
1746
+ "",
1747
+ "Read referenced files in full if a finding depends on surrounding context (RLS policies, validation schemas, audit-log helpers).",
1748
+ "",
1749
+ "When ready, emit your verdict as the fenced JSON block specified in the system prompt. No prose after the closing fence."
1750
+ ].join("\n");
1751
+ }
1752
+
1753
+ // src/agent/pr-review-service.ts
1754
+ var PrReviewError = class extends Error {
1755
+ code;
1756
+ excerpt;
1757
+ constructor(code, message, excerpt = "") {
1758
+ super(message);
1759
+ this.name = "PrReviewError";
1760
+ this.code = code;
1761
+ this.excerpt = excerpt;
1762
+ }
1763
+ };
1764
+ var STDERR_KEEP = 4e3;
1765
+ async function runPrReview(args) {
1766
+ const systemPrompt = buildPrReviewSystemPrompt(args);
1767
+ const userPrompt = buildPrReviewUserPrompt(args);
1768
+ const claude = args.claudePath ?? "claude";
1769
+ const cliArgs = [
1770
+ "--allowedTools",
1771
+ reviewAllowedToolsFlag(),
1772
+ "--system-prompt",
1773
+ systemPrompt,
1774
+ ...args.modelId ? ["--model", args.modelId] : [],
1775
+ userPrompt
1776
+ ];
1777
+ const logDir = join8(homedir5(), ".cache", "task", "runs");
1778
+ await mkdir7(logDir, { recursive: true }).catch(() => {
1779
+ });
1780
+ const logPath = join8(logDir, `${args.runId}.review.log`);
1781
+ await writeFile8(logPath, "").catch(() => {
1782
+ });
1783
+ const { createWriteStream } = await import("fs");
1784
+ const logHandle = createWriteStream(logPath, { flags: "a" });
1785
+ let stdoutBuffer = "";
1786
+ let stderrTail = "";
1787
+ const exitCode = await new Promise((resolve2, reject) => {
1788
+ const child = spawn2(claude, cliArgs, {
1789
+ cwd: args.cwd,
1790
+ stdio: ["ignore", "pipe", "pipe"],
1791
+ env: { ...process.env, FORCE_COLOR: args.silent ? "0" : "1" }
1792
+ });
1793
+ child.on("error", (err) => {
1794
+ logHandle.end();
1795
+ reject(new PrReviewError("spawn_failed", `Could not spawn claude: ${err.message}`));
1796
+ });
1797
+ child.stdout?.on("data", (chunk) => {
1798
+ stdoutBuffer += chunk.toString("utf8");
1799
+ if (args.silent) {
1800
+ logHandle.write(chunk);
1801
+ } else {
1802
+ process.stdout.write(chunk);
1803
+ logHandle.write(chunk);
1804
+ }
1805
+ });
1806
+ child.stderr?.on("data", (chunk) => {
1807
+ if (args.silent) {
1808
+ logHandle.write(chunk);
1809
+ } else {
1810
+ process.stderr.write(chunk);
1811
+ logHandle.write(chunk);
1812
+ }
1813
+ stderrTail = (stderrTail + chunk.toString("utf8")).slice(-STDERR_KEEP);
1814
+ });
1815
+ child.on("close", (code) => {
1816
+ logHandle.end();
1817
+ resolve2(code ?? 0);
1818
+ });
1819
+ });
1820
+ if (exitCode !== 0) {
1821
+ throw new PrReviewError(
1822
+ "nonzero_exit",
1823
+ `claude review subprocess exited with code ${exitCode}`,
1824
+ stderrTail
1825
+ );
1826
+ }
1827
+ return parseVerdict(stdoutBuffer, logPath);
1828
+ }
1829
+ function parseVerdict(stdout, logPath) {
1830
+ const blocks = extractJsonBlocks(stdout);
1831
+ if (blocks.length === 0) {
1832
+ throw new PrReviewError(
1833
+ "no_json_block",
1834
+ "Could not find a fenced ```json block in the review subprocess output",
1835
+ stdout.slice(-2e3)
1836
+ );
1837
+ }
1838
+ const rawJson = blocks[blocks.length - 1];
1839
+ let parsed;
1840
+ try {
1841
+ parsed = JSON.parse(rawJson);
1842
+ } catch (err) {
1843
+ throw new PrReviewError(
1844
+ "invalid_json",
1845
+ `JSON.parse failed: ${err.message}`,
1846
+ rawJson.slice(0, 2e3)
1847
+ );
1848
+ }
1849
+ const verdict = normaliseVerdict(parsed, rawJson, logPath);
1850
+ return verdict;
1851
+ }
1852
+ function extractJsonBlocks(text) {
1853
+ const re = /```(?:json|JSON|jsonc)\s*\n([\s\S]*?)\n```/g;
1854
+ const out = [];
1855
+ let match;
1856
+ while ((match = re.exec(text)) !== null) {
1857
+ if (match[1] !== void 0) out.push(match[1]);
1858
+ }
1859
+ return out;
1860
+ }
1861
+ var QA_VERDICTS = /* @__PURE__ */ new Set(["GO", "CONDITIONAL", "NO-GO"]);
1862
+ var SECURITY_VERDICTS = /* @__PURE__ */ new Set(["PASS", "PASS_WITH_CONDITIONS", "FAIL"]);
1863
+ var SECURITY_LAYER_VERDICTS = /* @__PURE__ */ new Set(["PASS", "FAIL"]);
1864
+ var SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
1865
+ var LAYER_KEYS = [
1866
+ "authentication",
1867
+ "tenant_resolution",
1868
+ "authorisation",
1869
+ "rls",
1870
+ "audit"
1871
+ ];
1872
+ function normaliseVerdict(raw, rawJson, logPath) {
1873
+ if (raw === null || typeof raw !== "object") {
1874
+ throw new PrReviewError(
1875
+ "schema_mismatch",
1876
+ "Verdict JSON is not an object",
1877
+ rawJson.slice(0, 1e3)
1878
+ );
1879
+ }
1880
+ const r = raw;
1881
+ const qaRaw = r["qa"] ?? {};
1882
+ const secRaw = r["security"] ?? {};
1883
+ const layersRaw = secRaw["layers_reviewed"] ?? {};
1884
+ const qaVerdict = String(qaRaw["verdict"] ?? "");
1885
+ if (!QA_VERDICTS.has(qaVerdict)) {
1886
+ throw new PrReviewError(
1887
+ "schema_mismatch",
1888
+ `qa.verdict is "${qaVerdict}", expected one of GO / CONDITIONAL / NO-GO`,
1889
+ rawJson.slice(0, 1e3)
1890
+ );
1891
+ }
1892
+ const securityVerdict = String(secRaw["verdict"] ?? "");
1893
+ if (!SECURITY_VERDICTS.has(securityVerdict)) {
1894
+ throw new PrReviewError(
1895
+ "schema_mismatch",
1896
+ `security.verdict is "${securityVerdict}", expected PASS / PASS_WITH_CONDITIONS / FAIL`,
1897
+ rawJson.slice(0, 1e3)
1898
+ );
1899
+ }
1900
+ const layers = {};
1901
+ for (const key of LAYER_KEYS) {
1902
+ const val = String(layersRaw[key] ?? "");
1903
+ layers[key] = SECURITY_LAYER_VERDICTS.has(val) ? val : "FAIL";
1904
+ }
1905
+ const findingsRaw = Array.isArray(r["findings"]) ? r["findings"] : [];
1906
+ const findings = findingsRaw.slice(0, 50).map((f) => {
1907
+ const fr = f ?? {};
1908
+ const severity = String(fr["severity"] ?? "low");
1909
+ const lensRaw = String(fr["lens"] ?? "qa");
1910
+ return {
1911
+ lens: lensRaw === "security" ? "security" : "qa",
1912
+ severity: SEVERITIES.has(severity) ? severity : "low",
1913
+ file: typeof fr["file"] === "string" ? fr["file"].slice(0, 500) : null,
1914
+ description: String(fr["description"] ?? "").slice(0, 2e3),
1915
+ recommendation: String(fr["recommendation"] ?? "").slice(0, 2e3)
1916
+ };
1917
+ });
1918
+ const failedChecks = Array.isArray(qaRaw["failed_checks"]) ? qaRaw["failed_checks"].map((s) => String(s).slice(0, 200)).slice(0, 20) : [];
1919
+ const overall = qaVerdict === "GO" && securityVerdict === "PASS" ? "pass" : "fail";
1920
+ return {
1921
+ overall,
1922
+ summary: String(r["summary"] ?? "").slice(0, 1e3) || (overall === "pass" ? "Review passed." : "Review failed."),
1923
+ qa: { verdict: qaVerdict, failed_checks: failedChecks },
1924
+ security: { verdict: securityVerdict, layers_reviewed: layers },
1925
+ findings,
1926
+ rawJson: rawJson.slice(0, 5e4),
1927
+ logPath
1928
+ };
1929
+ }
1930
+
1618
1931
  // src/guardrail/diff-check.ts
1619
1932
  import { execFileSync as execFileSync4 } from "child_process";
1620
1933
 
@@ -1705,7 +2018,7 @@ function discardWorkingTreeChanges(cwd) {
1705
2018
  }
1706
2019
 
1707
2020
  // src/test-runner/run-tests.ts
1708
- import { spawn as spawn2 } from "child_process";
2021
+ import { spawn as spawn3 } from "child_process";
1709
2022
  var ALLOWED_EXECUTABLES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun", "node", "npx"]);
1710
2023
  var DEFAULT_COMMAND = "pnpm typecheck";
1711
2024
  var TIMEOUT_MS = 10 * 60 * 1e3;
@@ -1733,7 +2046,7 @@ async function runProjectTest(args) {
1733
2046
  const [exe, ...rest] = argv;
1734
2047
  const startedAt = Date.now();
1735
2048
  return new Promise((resolve2) => {
1736
- const child = spawn2(exe, rest, {
2049
+ const child = spawn3(exe, rest, {
1737
2050
  cwd: args.cwd,
1738
2051
  stdio: ["ignore", "pipe", "pipe"],
1739
2052
  shell: false,
@@ -1778,16 +2091,16 @@ async function runProjectTest(args) {
1778
2091
  }
1779
2092
 
1780
2093
  // src/test-runner/auto-install.ts
1781
- import { spawn as spawn3 } from "child_process";
2094
+ import { spawn as spawn4 } from "child_process";
1782
2095
  import { stat as stat2 } from "fs/promises";
1783
- import { dirname as dirname4, join as join8 } from "path";
2096
+ import { dirname as dirname4, join as join9 } from "path";
1784
2097
  var INSTALL_TIMEOUT_MS = 10 * 60 * 1e3;
1785
2098
  var TAIL_BYTES2 = 4e3;
1786
2099
  async function findPnpmWorkspaceRoot(start) {
1787
2100
  let cur = start;
1788
2101
  for (; ; ) {
1789
2102
  try {
1790
- await stat2(join8(cur, "pnpm-lock.yaml"));
2103
+ await stat2(join9(cur, "pnpm-lock.yaml"));
1791
2104
  return cur;
1792
2105
  } catch {
1793
2106
  }
@@ -1817,8 +2130,8 @@ async function ensureWorkspaceInstalled(args) {
1817
2130
  tail: ""
1818
2131
  };
1819
2132
  }
1820
- const lockMtime = await mtimeMs(join8(workspaceRoot, "pnpm-lock.yaml"));
1821
- const markerMtime = await mtimeMs(join8(workspaceRoot, "node_modules", ".modules.yaml"));
2133
+ const lockMtime = await mtimeMs(join9(workspaceRoot, "pnpm-lock.yaml"));
2134
+ const markerMtime = await mtimeMs(join9(workspaceRoot, "node_modules", ".modules.yaml"));
1822
2135
  if (lockMtime !== null && markerMtime !== null && markerMtime >= lockMtime) {
1823
2136
  return {
1824
2137
  ok: true,
@@ -1832,7 +2145,7 @@ async function ensureWorkspaceInstalled(args) {
1832
2145
  }
1833
2146
  const reason = markerMtime === null ? "node_modules missing \u2014 cold install" : "pnpm-lock.yaml newer than install marker";
1834
2147
  return new Promise((resolve2) => {
1835
- const child = spawn3("pnpm", ["install", "--frozen-lockfile"], {
2148
+ const child = spawn4("pnpm", ["install", "--frozen-lockfile"], {
1836
2149
  cwd: workspaceRoot,
1837
2150
  stdio: ["ignore", "pipe", "pipe"],
1838
2151
  shell: false,
@@ -1878,10 +2191,10 @@ async function ensureWorkspaceInstalled(args) {
1878
2191
  }
1879
2192
 
1880
2193
  // src/util/progress.ts
1881
- import { mkdir as mkdir7, writeFile as writeFile8, rename as rename2, unlink as unlink3, readdir, stat as stat3 } from "fs/promises";
2194
+ import { mkdir as mkdir8, writeFile as writeFile9, rename as rename2, unlink as unlink3, readdir, stat as stat3 } from "fs/promises";
1882
2195
  import { tmpdir } from "os";
1883
- import { join as join9 } from "path";
1884
- var PROGRESS_DIR = join9(tmpdir(), "task-progress");
2196
+ import { join as join10 } from "path";
2197
+ var PROGRESS_DIR = join10(tmpdir(), "task-progress");
1885
2198
  var STALE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
1886
2199
  var ProgressWriter = class {
1887
2200
  path;
@@ -1894,7 +2207,7 @@ var ProgressWriter = class {
1894
2207
  this.ticketId = ticketId;
1895
2208
  const deliveryId = process.env["TASK_DELIVERY_ID"]?.trim();
1896
2209
  const fileBase = deliveryId && deliveryId.length > 0 ? deliveryId : `manual-${process.pid}`;
1897
- this.path = join9(PROGRESS_DIR, `${fileBase}.json`);
2210
+ this.path = join10(PROGRESS_DIR, `${fileBase}.json`);
1898
2211
  }
1899
2212
  /** Switch the in-flight ticket between phases (used by `task scan` which iterates). */
1900
2213
  setTicketId(ticketId) {
@@ -1928,12 +2241,12 @@ var ProgressWriter = class {
1928
2241
  });
1929
2242
  }
1930
2243
  async writeAtomic(payload) {
1931
- await mkdir7(PROGRESS_DIR, { recursive: true }).catch(() => {
2244
+ await mkdir8(PROGRESS_DIR, { recursive: true }).catch(() => {
1932
2245
  });
1933
2246
  const body = JSON.stringify(payload);
1934
2247
  const tmp = `${this.path}.tmp`;
1935
2248
  try {
1936
- await writeFile8(tmp, body, { encoding: "utf8", mode: 384 });
2249
+ await writeFile9(tmp, body, { encoding: "utf8", mode: 384 });
1937
2250
  await rename2(tmp, this.path);
1938
2251
  } catch {
1939
2252
  await unlink3(tmp).catch(() => {
@@ -1950,7 +2263,7 @@ var ProgressWriter = class {
1950
2263
  const cutoff = Date.now() - STALE_MAX_AGE_MS;
1951
2264
  await Promise.all(
1952
2265
  entries.filter((name) => name.endsWith(".json") || name.endsWith(".json.tmp")).map(async (name) => {
1953
- const p = join9(PROGRESS_DIR, name);
2266
+ const p = join10(PROGRESS_DIR, name);
1954
2267
  try {
1955
2268
  const s = await stat3(p);
1956
2269
  if (s.mtimeMs < cutoff) {
@@ -1992,6 +2305,9 @@ function registerWork(program2) {
1992
2305
  ).option(
1993
2306
  "--confirm",
1994
2307
  "Confirm --reset in non-TTY (silent / scheduled-task) contexts. Has no effect without --reset."
2308
+ ).option(
2309
+ "--no-auto-review",
2310
+ "Skip the post-PR /qa + /security auto-review. The PR is left open for a human to review and merge."
1995
2311
  ).option("--schedule-id <id>", "Internal: schedule id when invoked from a scheduled task").action(async (ticketId, opts) => {
1996
2312
  await runWork(ticketId, opts);
1997
2313
  });
@@ -2515,6 +2831,86 @@ Claude session: ${runId}
2515
2831
  if (err instanceof CliError) err.phase = "post_push";
2516
2832
  throw err;
2517
2833
  }
2834
+ const autoReviewEnabled = opts.autoReview !== false;
2835
+ if (autoReviewEnabled && prNumber !== void 0 && prUrl !== void 0 && !opts.dryRun) {
2836
+ await progress.setPhase("auto_reviewing");
2837
+ if (!silent) {
2838
+ process.stdout.write(c.dim(" Auto-reviewing PR \u2014 /qa + /security\u2026\n"));
2839
+ }
2840
+ try {
2841
+ const verdict = await runPrReview({
2842
+ cwd,
2843
+ runId,
2844
+ silent,
2845
+ baseBranch: ticketBaseBranch,
2846
+ branchName,
2847
+ prNumber,
2848
+ prUrl,
2849
+ ...ctx.localCfg.claude_path ? { claudePath: ctx.localCfg.claude_path } : {}
2850
+ });
2851
+ const verdictResp = await apiCallOrThrow(
2852
+ "POST",
2853
+ `/api/v1/cli/me/tickets/${detail.id}/pull-requests/${prNumber}/auto-review-verdict`,
2854
+ {
2855
+ body: {
2856
+ overall: verdict.overall,
2857
+ summary: verdict.summary,
2858
+ qa: verdict.qa,
2859
+ security: verdict.security,
2860
+ findings: verdict.findings,
2861
+ raw_json: verdict.rawJson,
2862
+ claude_session_id: runId
2863
+ }
2864
+ }
2865
+ );
2866
+ await apiCall("POST", "/api/v1/cli/me/runs", {
2867
+ body: {
2868
+ ticket_id: detail.id,
2869
+ schedule_id: opts.scheduleId,
2870
+ event: verdict.overall === "pass" ? "auto_review_passed" : "auto_review_failed",
2871
+ claude_session_id: runId,
2872
+ output_excerpt: `qa=${verdict.qa.verdict} security=${verdict.security.verdict}` + (verdict.findings.length > 0 ? ` | ${verdict.findings.length} finding(s)` : "")
2873
+ }
2874
+ });
2875
+ if (!silent) {
2876
+ const icon = verdict.overall === "pass" ? c.ok("\u2713 Auto-review PASS") : c.warn("\u2717 Auto-review FAIL");
2877
+ process.stdout.write(
2878
+ `${icon} qa=${verdict.qa.verdict} security=${verdict.security.verdict}
2879
+ `
2880
+ );
2881
+ if (verdict.overall === "pass") {
2882
+ process.stdout.write(
2883
+ c.dim(
2884
+ verdictResp.status === "queued_for_merge" ? " PR approved \u2014 queued for merge to " : " Verdict recorded; merge queue update: "
2885
+ ) + c.cyan(`${ticketBaseBranch}
2886
+ `)
2887
+ );
2888
+ } else {
2889
+ process.stdout.write(
2890
+ c.dim(` ${verdict.findings.length} finding(s) posted as PR comment; PR left open.
2891
+ `)
2892
+ );
2893
+ }
2894
+ }
2895
+ } catch (err) {
2896
+ const excerpt = err instanceof PrReviewError ? `${err.code}: ${err.message.slice(0, 1e3)}` : err.message.slice(0, 1e3);
2897
+ await apiCall("POST", "/api/v1/cli/me/runs", {
2898
+ body: {
2899
+ ticket_id: detail.id,
2900
+ schedule_id: opts.scheduleId,
2901
+ event: "auto_review_errored",
2902
+ claude_session_id: runId,
2903
+ output_excerpt: excerpt
2904
+ }
2905
+ });
2906
+ if (!silent) {
2907
+ process.stdout.write(
2908
+ `${c.warn("! Auto-review skipped")} ${c.dim(excerpt.slice(0, 200))}
2909
+ ` + c.dim(" PR left open for manual review.\n")
2910
+ );
2911
+ }
2912
+ }
2913
+ }
2518
2914
  try {
2519
2915
  checkoutBranch(cwd, baseBranch);
2520
2916
  } catch {
@@ -3431,10 +3827,10 @@ function autopilotExitCode(code, status) {
3431
3827
  }
3432
3828
 
3433
3829
  // src/scan/llm.ts
3434
- import { spawn as spawn4 } from "child_process";
3435
- import { mkdir as mkdir8, writeFile as writeFile9 } from "fs/promises";
3436
- import { homedir as homedir5 } from "os";
3437
- import { join as join10 } from "path";
3830
+ import { spawn as spawn5 } from "child_process";
3831
+ import { mkdir as mkdir9, writeFile as writeFile10 } from "fs/promises";
3832
+ import { homedir as homedir6 } from "os";
3833
+ import { join as join11 } from "path";
3438
3834
  var FIX_PROMPT_JSON_SCHEMA = {
3439
3835
  type: "object",
3440
3836
  // Phase 3 — confidence_reason is REQUIRED unconditionally so the
@@ -3523,7 +3919,7 @@ async function generateFixPromptJson(args) {
3523
3919
  return new Promise((resolve2, reject) => {
3524
3920
  let child;
3525
3921
  try {
3526
- child = spawn4(claude, cliArgs, {
3922
+ child = spawn5(claude, cliArgs, {
3527
3923
  stdio: ["pipe", "pipe", "pipe"],
3528
3924
  signal: args.signal
3529
3925
  });
@@ -3649,10 +4045,10 @@ function readEnvelopeTokens(raw, userPrompt, innerText) {
3649
4045
  async function maybeDumpDebug(ticketId, stdout, stderr) {
3650
4046
  if (!DEBUG && stdout.length === 0 && stderr.length === 0) return null;
3651
4047
  try {
3652
- const dir = join10(homedir5(), ".cache", "task", "scan-debug");
3653
- await mkdir8(dir, { recursive: true });
3654
- const path = join10(dir, `${ticketId}-${Date.now()}.log`);
3655
- await writeFile9(
4048
+ const dir = join11(homedir6(), ".cache", "task", "scan-debug");
4049
+ await mkdir9(dir, { recursive: true });
4050
+ const path = join11(dir, `${ticketId}-${Date.now()}.log`);
4051
+ await writeFile10(
3656
4052
  path,
3657
4053
  ["## ticket_id", ticketId, "", "## stdout", stdout, "", "## stderr", stderr].join("\n")
3658
4054
  );
@@ -3998,7 +4394,7 @@ function clampInt(raw, min, max, fallback) {
3998
4394
  }
3999
4395
 
4000
4396
  // src/commands/slack-import.ts
4001
- import { spawn as spawn5 } from "child_process";
4397
+ import { spawn as spawn6 } from "child_process";
4002
4398
  import { request as request5 } from "undici";
4003
4399
  var BULLET_TYPES = ["bug", "feature", "task", "question", "improvement"];
4004
4400
  var BULLET_PRIORITIES = ["critical", "high", "medium", "low", "none"];
@@ -4135,7 +4531,7 @@ async function generateBullets(args) {
4135
4531
  return new Promise((resolve2, reject) => {
4136
4532
  let child;
4137
4533
  try {
4138
- child = spawn5(claude, cliArgs, { stdio: ["pipe", "pipe", "pipe"] });
4534
+ child = spawn6(claude, cliArgs, { stdio: ["pipe", "pipe", "pipe"] });
4139
4535
  } catch (err) {
4140
4536
  reject(new Error(`Could not invoke claude: ${err.message}`));
4141
4537
  return;
@@ -4239,7 +4635,10 @@ function registerFastTrack(program2) {
4239
4635
  ).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(
4240
4636
  "--reset",
4241
4637
  "DESTRUCTIVE: discard local working-tree changes before the first ticket. Requires --confirm in non-TTY contexts."
4242
- ).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) => {
4638
+ ).option("--confirm", "Confirm --reset in non-TTY (silent / scheduled-task) contexts").option(
4639
+ "--no-auto-review",
4640
+ "Skip the post-PR /qa + /security auto-review. Each PR is left open for a human to review and merge."
4641
+ ).option("--schedule-id <id>", "Internal: schedule id when invoked from a scheduled task").action(async (opts) => {
4243
4642
  await runFastTrack(opts);
4244
4643
  });
4245
4644
  }
@@ -4280,7 +4679,11 @@ async function runFastTrack(opts) {
4280
4679
  ...opts.dryRun ? { dryRun: true } : {},
4281
4680
  ...opts.scheduleId ? { scheduleId: opts.scheduleId } : {},
4282
4681
  ...firstIteration && opts.reset ? { reset: true } : {},
4283
- ...firstIteration && opts.confirm ? { confirm: true } : {}
4682
+ ...firstIteration && opts.confirm ? { confirm: true } : {},
4683
+ // Commander sets opts.autoReview to `false` only when --no-auto-review
4684
+ // is passed; otherwise it's `undefined` and processOneTicketImpl
4685
+ // treats `undefined` as "auto-review enabled" (opts.autoReview !== false).
4686
+ ...opts.autoReview === false ? { autoReview: false } : {}
4284
4687
  };
4285
4688
  const outcome = await fastTrackOneTicket({
4286
4689
  api,
@@ -4620,10 +5023,10 @@ import { randomUUID as randomUUID4 } from "crypto";
4620
5023
  import { platform as platform2 } from "os";
4621
5024
 
4622
5025
  // src/scheduler/launchd.ts
4623
- import { mkdir as mkdir9, readFile as readFile5, writeFile as writeFile10, unlink as unlink4, readdir as readdir2 } from "fs/promises";
4624
- import { homedir as homedir6 } from "os";
4625
- import { join as join11 } from "path";
4626
- import { execFileSync as execFileSync9, spawn as spawn6 } from "child_process";
5026
+ import { mkdir as mkdir10, readFile as readFile5, writeFile as writeFile11, unlink as unlink4, readdir as readdir2 } from "fs/promises";
5027
+ import { homedir as homedir7 } from "os";
5028
+ import { join as join12 } from "path";
5029
+ import { execFileSync as execFileSync9, spawn as spawn7 } from "child_process";
4627
5030
 
4628
5031
  // src/scheduler/cron-translate.ts
4629
5032
  function translateToLaunchd(cron) {
@@ -4724,14 +5127,14 @@ function expandField(field, min, max) {
4724
5127
  }
4725
5128
 
4726
5129
  // src/scheduler/launchd.ts
4727
- var PLIST_DIR = join11(homedir6(), "Library", "LaunchAgents");
5130
+ var PLIST_DIR = join12(homedir7(), "Library", "LaunchAgents");
4728
5131
  var LABEL_PREFIX = "com.inteeka.task.cli.";
4729
5132
  var SAFE_ID_RE = /^[0-9a-zA-Z._-]+$/;
4730
5133
  function plistPath(id) {
4731
5134
  if (!SAFE_ID_RE.test(id) || id.includes("..")) {
4732
5135
  throw new Error(`Refusing to compute plist path for unsafe id: ${id}`);
4733
5136
  }
4734
- return join11(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
5137
+ return join12(PLIST_DIR, `${LABEL_PREFIX}${id}.plist`);
4735
5138
  }
4736
5139
  function buildPlist(entry) {
4737
5140
  const calendars = translateToLaunchd(entry.cron);
@@ -4767,9 +5170,9 @@ ${fields}
4767
5170
  ` <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>`,
4768
5171
  ` </dict>`,
4769
5172
  ` <key>StandardOutPath</key>`,
4770
- ` <string>${escapeXml(join11(homedir6(), ".cache", "task", "launchd-stdout.log"))}</string>`,
5173
+ ` <string>${escapeXml(join12(homedir7(), ".cache", "task", "launchd-stdout.log"))}</string>`,
4771
5174
  ` <key>StandardErrorPath</key>`,
4772
- ` <string>${escapeXml(join11(homedir6(), ".cache", "task", "launchd-stderr.log"))}</string>`,
5175
+ ` <string>${escapeXml(join12(homedir7(), ".cache", "task", "launchd-stderr.log"))}</string>`,
4773
5176
  !entry.enabled ? ` <key>Disabled</key>
4774
5177
  <true/>` : "",
4775
5178
  "</dict>",
@@ -4787,9 +5190,9 @@ function bootstrapDomain() {
4787
5190
  }
4788
5191
  var launchdAdapter = {
4789
5192
  async upsert(entry) {
4790
- await mkdir9(PLIST_DIR, { recursive: true });
5193
+ await mkdir10(PLIST_DIR, { recursive: true });
4791
5194
  const path = plistPath(entry.id);
4792
- await writeFile10(path, buildPlist(entry));
5195
+ await writeFile11(path, buildPlist(entry));
4793
5196
  try {
4794
5197
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
4795
5198
  } catch {
@@ -4818,7 +5221,7 @@ var launchdAdapter = {
4818
5221
  for (const file of ours) {
4819
5222
  const id = file.slice(LABEL_PREFIX.length, -".plist".length);
4820
5223
  try {
4821
- const xml = await readFile5(join11(PLIST_DIR, file), "utf8");
5224
+ const xml = await readFile5(join12(PLIST_DIR, file), "utf8");
4822
5225
  const cron = xml.match(/<key>StartCalendarInterval<\/key>[\s\S]*?<\/array>/)?.[0] ?? "";
4823
5226
  const command = xml.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/)?.[1] ?? "";
4824
5227
  const disabled = /<key>Disabled<\/key>\s*<true\/>/.test(xml);
@@ -4841,7 +5244,7 @@ var launchdAdapter = {
4841
5244
  return new Promise((resolve2) => {
4842
5245
  const args = entry.command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [entry.command];
4843
5246
  const cmd = args.shift() ?? entry.command;
4844
- const child = spawn6(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
5247
+ const child = spawn7(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
4845
5248
  let stdoutTail = "";
4846
5249
  let stderrTail = "";
4847
5250
  child.stdout?.on("data", (chunk) => {
@@ -4864,7 +5267,7 @@ var launchdAdapter = {
4864
5267
  }
4865
5268
  if (enabled) {
4866
5269
  xml = xml.replace(/\s*<key>Disabled<\/key>\s*<true\/>/, "");
4867
- await writeFile10(path, xml);
5270
+ await writeFile11(path, xml);
4868
5271
  try {
4869
5272
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
4870
5273
  } catch {
@@ -4876,7 +5279,7 @@ var launchdAdapter = {
4876
5279
  "</dict>\n</plist>",
4877
5280
  " <key>Disabled</key>\n <true/>\n</dict>\n</plist>"
4878
5281
  );
4879
- await writeFile10(path, xml);
5282
+ await writeFile11(path, xml);
4880
5283
  }
4881
5284
  try {
4882
5285
  execFileSync9("launchctl", ["bootout", bootstrapDomain(), path], { stdio: "ignore" });
@@ -4887,7 +5290,7 @@ var launchdAdapter = {
4887
5290
  };
4888
5291
 
4889
5292
  // src/scheduler/cron.ts
4890
- import { execFileSync as execFileSync10, spawn as spawn7 } from "child_process";
5293
+ import { execFileSync as execFileSync10, spawn as spawn8 } from "child_process";
4891
5294
 
4892
5295
  // src/scheduler/safe-command.ts
4893
5296
  var FORBIDDEN = /[;&|`$()<>\\]/;
@@ -4948,7 +5351,7 @@ function readCrontab() {
4948
5351
  }
4949
5352
  }
4950
5353
  function writeCrontab(text) {
4951
- const child = spawn7("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
5354
+ const child = spawn8("crontab", ["-"], { stdio: ["pipe", "inherit", "inherit"] });
4952
5355
  child.stdin.write(text);
4953
5356
  child.stdin.end();
4954
5357
  }
@@ -5029,7 +5432,7 @@ var cronAdapter = {
5029
5432
  return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
5030
5433
  }
5031
5434
  return new Promise((resolve2) => {
5032
- const child = spawn7(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
5435
+ const child = spawn8(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
5033
5436
  let stdoutTail = "";
5034
5437
  let stderrTail = "";
5035
5438
  child.stdout?.on(
@@ -5057,7 +5460,7 @@ var cronAdapter = {
5057
5460
  };
5058
5461
 
5059
5462
  // src/scheduler/windows.ts
5060
- import { execFileSync as execFileSync11, spawn as spawn8 } from "child_process";
5463
+ import { execFileSync as execFileSync11, spawn as spawn9 } from "child_process";
5061
5464
  var TASK_PREFIX = "TaskCLI_";
5062
5465
  function taskName(id) {
5063
5466
  return `${TASK_PREFIX}${id.replace(/[^A-Za-z0-9_-]/g, "_")}`;
@@ -5170,7 +5573,7 @@ var windowsAdapter = {
5170
5573
  return Promise.resolve({ exitCode: 1, stdoutTail: "", stderrTail: `rejected: ${reason}` });
5171
5574
  }
5172
5575
  return new Promise((resolve2) => {
5173
- const child = spawn8(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
5576
+ const child = spawn9(parsed.bin, parsed.args, { stdio: ["ignore", "pipe", "pipe"] });
5174
5577
  let stdoutTail = "";
5175
5578
  let stderrTail = "";
5176
5579
  child.stdout?.on(
@@ -5229,10 +5632,10 @@ var unsupportedAdapter = {
5229
5632
  };
5230
5633
 
5231
5634
  // src/scheduler/registry.ts
5232
- import { mkdir as mkdir10, readFile as readFile6, writeFile as writeFile11 } from "fs/promises";
5233
- import { homedir as homedir7 } from "os";
5234
- import { dirname as dirname5, join as join12 } from "path";
5235
- var REGISTRY_PATH = join12(homedir7(), ".config", "task", "schedules.json");
5635
+ import { mkdir as mkdir11, readFile as readFile6, writeFile as writeFile12 } from "fs/promises";
5636
+ import { homedir as homedir8 } from "os";
5637
+ import { dirname as dirname5, join as join13 } from "path";
5638
+ var REGISTRY_PATH = join13(homedir8(), ".config", "task", "schedules.json");
5236
5639
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5237
5640
  function looksLikeRegistryRow(value) {
5238
5641
  if (!value || typeof value !== "object") return false;
@@ -5252,8 +5655,8 @@ async function readRegistry() {
5252
5655
  }
5253
5656
  }
5254
5657
  async function writeRegistry(rows) {
5255
- await mkdir10(dirname5(REGISTRY_PATH), { recursive: true });
5256
- await writeFile11(REGISTRY_PATH, JSON.stringify(rows, null, 2));
5658
+ await mkdir11(dirname5(REGISTRY_PATH), { recursive: true });
5659
+ await writeFile12(REGISTRY_PATH, JSON.stringify(rows, null, 2));
5257
5660
  }
5258
5661
  async function upsertRegistry(row) {
5259
5662
  if (!UUID_RE.test(row.id)) {
@@ -5493,8 +5896,8 @@ function stripAnsi(s) {
5493
5896
 
5494
5897
  // src/commands/runs.ts
5495
5898
  import { readFile as readFile7 } from "fs/promises";
5496
- import { homedir as homedir8 } from "os";
5497
- import { join as join13 } from "path";
5899
+ import { homedir as homedir9 } from "os";
5900
+ import { join as join14 } from "path";
5498
5901
  function registerRuns(program2) {
5499
5902
  const cmd = program2.command("runs").description("Inspect agentic CLI run history");
5500
5903
  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) => {
@@ -5523,7 +5926,7 @@ function registerRuns(program2) {
5523
5926
  process.stdout.write(JSON.stringify(row, null, 2) + "\n");
5524
5927
  });
5525
5928
  cmd.command("logs <id>").description("Show captured agent output for a run, if available").action(async (id) => {
5526
- const localPath = join13(homedir8(), ".cache", "task", "runs", `${id}.log`);
5929
+ const localPath = join14(homedir9(), ".cache", "task", "runs", `${id}.log`);
5527
5930
  try {
5528
5931
  const text = await readFile7(localPath, "utf8");
5529
5932
  process.stdout.write(text);
@@ -5596,8 +5999,8 @@ function registerConfig(program2) {
5596
5999
 
5597
6000
  // src/commands/doctor.ts
5598
6001
  import { execFileSync as execFileSync12 } from "child_process";
5599
- import { readFile as readFile8, writeFile as writeFile12 } from "fs/promises";
5600
- import { join as join14 } from "path";
6002
+ import { readFile as readFile8, writeFile as writeFile13 } from "fs/promises";
6003
+ import { join as join15 } from "path";
5601
6004
  import { request as request6 } from "undici";
5602
6005
  var ALLOWED_TEST_EXECUTABLES = /* @__PURE__ */ new Set(["pnpm", "npm", "yarn", "bun", "node", "npx"]);
5603
6006
  var DEFAULT_TEST_COMMAND = "pnpm typecheck";
@@ -5811,7 +6214,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
5811
6214
  detail: `${command} (non-script executable, not statically verifiable)`
5812
6215
  };
5813
6216
  }
5814
- const pkgPath = join14(root, "package.json");
6217
+ const pkgPath = join15(root, "package.json");
5815
6218
  let pkgRaw;
5816
6219
  try {
5817
6220
  pkgRaw = await readFile8(pkgPath, "utf8");
@@ -5847,7 +6250,7 @@ async function checkPrePushTest(root, configuredCommand, fix) {
5847
6250
  pkg.scripts = { ...scripts, typecheck: "tsc --noEmit" };
5848
6251
  const indent = detectIndent(pkgRaw);
5849
6252
  const trailingNewline = pkgRaw.endsWith("\n") ? "\n" : "";
5850
- await writeFile12(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
6253
+ await writeFile13(pkgPath, JSON.stringify(pkg, null, indent) + trailingNewline);
5851
6254
  return {
5852
6255
  name: "pre-push test",
5853
6256
  ok: true,
@@ -5896,7 +6299,7 @@ function checkBinary(name, command) {
5896
6299
  }
5897
6300
 
5898
6301
  // src/commands/version.ts
5899
- var CLI_VERSION = true ? "0.2.26" : "0.0.0-dev";
6302
+ var CLI_VERSION = true ? "0.2.27" : "0.0.0-dev";
5900
6303
  function registerVersion(program2) {
5901
6304
  program2.command("version").description("Print the CLI version").action(() => {
5902
6305
  process.stdout.write(CLI_VERSION + "\n");