@staff0rd/assist 0.95.0 → 0.96.0

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/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.95.0",
9
+ version: "0.96.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -15,7 +15,8 @@ var package_default = {
15
15
  },
16
16
  files: [
17
17
  "dist",
18
- "claude"
18
+ "claude",
19
+ "assist.cli-reads"
19
20
  ],
20
21
  publishConfig: {
21
22
  access: "public"
@@ -23,7 +24,8 @@ var package_default = {
23
24
  scripts: {
24
25
  build: "tsup",
25
26
  start: "node dist/index.js",
26
- "check:types": "tsc --noEmit"
27
+ "check:types": "tsc --noEmit",
28
+ test: "vitest run"
27
29
  },
28
30
  keywords: [],
29
31
  author: "",
@@ -65,7 +67,8 @@ var package_default = {
65
67
  react: "^19.2.4",
66
68
  "react-dom": "^19.2.4",
67
69
  "semantic-release": "^25.0.2",
68
- tsup: "^8.5.1"
70
+ tsup: "^8.5.1",
71
+ vitest: "^4.0.18"
69
72
  }
70
73
  };
71
74
 
@@ -73,11 +76,24 @@ var package_default = {
73
76
  import { execSync } from "child_process";
74
77
 
75
78
  // src/shared/loadConfig.ts
76
- import { existsSync, readFileSync, writeFileSync } from "fs";
79
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
77
80
  import { homedir } from "os";
78
81
  import { basename, join } from "path";
79
82
  import chalk from "chalk";
80
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
83
+ import { stringify as stringifyYaml } from "yaml";
84
+
85
+ // src/shared/loadRawYaml.ts
86
+ import { existsSync, readFileSync } from "fs";
87
+ import { parse as parseYaml } from "yaml";
88
+ function loadRawYaml(path31) {
89
+ if (!existsSync(path31)) return {};
90
+ try {
91
+ const content = readFileSync(path31, "utf-8");
92
+ return parseYaml(content) || {};
93
+ } catch {
94
+ return {};
95
+ }
96
+ }
81
97
 
82
98
  // src/shared/types.ts
83
99
  import { z } from "zod";
@@ -151,7 +167,7 @@ var assistConfigSchema = z.strictObject({
151
167
  // src/shared/loadConfig.ts
152
168
  function getConfigPath() {
153
169
  const claudeConfigPath = join(process.cwd(), ".claude", "assist.yml");
154
- if (existsSync(claudeConfigPath)) {
170
+ if (existsSync2(claudeConfigPath)) {
155
171
  return claudeConfigPath;
156
172
  }
157
173
  return join(process.cwd(), "assist.yml");
@@ -159,26 +175,17 @@ function getConfigPath() {
159
175
  function getGlobalConfigPath() {
160
176
  return join(homedir(), ".assist.yml");
161
177
  }
162
- function loadRawConfig(path31) {
163
- if (!existsSync(path31)) return {};
164
- try {
165
- const content = readFileSync(path31, "utf-8");
166
- return parseYaml(content) || {};
167
- } catch {
168
- return {};
169
- }
170
- }
171
178
  function loadConfig() {
172
- const globalRaw = loadRawConfig(getGlobalConfigPath());
173
- const projectRaw = loadRawConfig(getConfigPath());
179
+ const globalRaw = loadRawYaml(getGlobalConfigPath());
180
+ const projectRaw = loadRawYaml(getConfigPath());
174
181
  const merged = { ...globalRaw, ...projectRaw };
175
182
  return assistConfigSchema.parse(merged);
176
183
  }
177
184
  function loadProjectConfig() {
178
- return loadRawConfig(getConfigPath());
185
+ return loadRawYaml(getConfigPath());
179
186
  }
180
187
  function loadGlobalConfigRaw() {
181
- return loadRawConfig(getGlobalConfigPath());
188
+ return loadRawYaml(getGlobalConfigPath());
182
189
  }
183
190
  function saveGlobalConfig(config) {
184
191
  writeFileSync(getGlobalConfigPath(), stringifyYaml(config, { lineWidth: 0 }));
@@ -193,9 +200,9 @@ function getRepoName() {
193
200
  return config.devlog.name;
194
201
  }
195
202
  const packageJsonPath = join(process.cwd(), "package.json");
196
- if (existsSync(packageJsonPath)) {
203
+ if (existsSync2(packageJsonPath)) {
197
204
  try {
198
- const content = readFileSync(packageJsonPath, "utf-8");
205
+ const content = readFileSync2(packageJsonPath, "utf-8");
199
206
  const pkg = JSON.parse(content);
200
207
  if (pkg.name) {
201
208
  return pkg.name;
@@ -647,7 +654,7 @@ import chalk12 from "chalk";
647
654
 
648
655
  // src/commands/lint/init.ts
649
656
  import { execSync as execSync4 } from "child_process";
650
- import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
657
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
651
658
  import { dirname as dirname5, join as join4 } from "path";
652
659
  import { fileURLToPath } from "url";
653
660
  import chalk11 from "chalk";
@@ -671,10 +678,10 @@ async function promptConfirm(message, initial = true) {
671
678
 
672
679
  // src/shared/removeEslint/index.ts
673
680
  import { execSync as execSync3 } from "child_process";
674
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
681
+ import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
675
682
 
676
683
  // src/shared/removeEslint/removeEslintConfigFiles.ts
677
- import { existsSync as existsSync4, unlinkSync } from "fs";
684
+ import { existsSync as existsSync5, unlinkSync } from "fs";
678
685
  var ESLINT_CONFIG_FILES = [
679
686
  "eslint.config.js",
680
687
  "eslint.config.mjs",
@@ -690,7 +697,7 @@ var ESLINT_CONFIG_FILES = [
690
697
  function removeEslintConfigFiles() {
691
698
  let removed = false;
692
699
  for (const configFile of ESLINT_CONFIG_FILES) {
693
- if (existsSync4(configFile)) {
700
+ if (existsSync5(configFile)) {
694
701
  unlinkSync(configFile);
695
702
  console.log(`Removed ${configFile}`);
696
703
  removed = true;
@@ -712,10 +719,10 @@ function removeEslint(options2 = {}) {
712
719
  }
713
720
  function removeEslintFromPackageJson(options2) {
714
721
  const packageJsonPath = "package.json";
715
- if (!existsSync5(packageJsonPath)) {
722
+ if (!existsSync6(packageJsonPath)) {
716
723
  return false;
717
724
  }
718
- const packageJson = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
725
+ const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
719
726
  let modified = false;
720
727
  modified = removeEslintDeps(packageJson.dependencies) || modified;
721
728
  modified = removeEslintDeps(packageJson.devDependencies) || modified;
@@ -785,17 +792,17 @@ var __dirname2 = dirname5(fileURLToPath(import.meta.url));
785
792
  async function init() {
786
793
  removeEslint();
787
794
  const biomeConfigPath = "biome.json";
788
- if (!existsSync6(biomeConfigPath)) {
795
+ if (!existsSync7(biomeConfigPath)) {
789
796
  console.log("Initializing Biome...");
790
797
  execSync4("npx @biomejs/biome init", { stdio: "inherit" });
791
798
  }
792
- if (!existsSync6(biomeConfigPath)) {
799
+ if (!existsSync7(biomeConfigPath)) {
793
800
  console.log("No biome.json found, skipping linter config");
794
801
  return;
795
802
  }
796
803
  const linterConfigPath = join4(__dirname2, "commands/lint/biome.linter.json");
797
- const linterConfig = JSON.parse(readFileSync5(linterConfigPath, "utf-8"));
798
- const biomeConfig = JSON.parse(readFileSync5(biomeConfigPath, "utf-8"));
804
+ const linterConfig = JSON.parse(readFileSync6(linterConfigPath, "utf-8"));
805
+ const biomeConfig = JSON.parse(readFileSync6(biomeConfigPath, "utf-8"));
799
806
  const oldContent = `${JSON.stringify(biomeConfig, null, 2)}
800
807
  `;
801
808
  biomeConfig.linter = linterConfig.linter;
@@ -1099,8 +1106,8 @@ function createSettingsJson() {
1099
1106
  "source.organizeImports.biome": "explicit"
1100
1107
  }
1101
1108
  };
1102
- const settingsPath2 = path9.join(process.cwd(), ".vscode", "settings.json");
1103
- fs3.writeFileSync(settingsPath2, `${JSON.stringify(settings, null, " ")}
1109
+ const settingsPath = path9.join(process.cwd(), ".vscode", "settings.json");
1110
+ fs3.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
1104
1111
  `);
1105
1112
  console.log(chalk16.green("Created .vscode/settings.json"));
1106
1113
  }
@@ -1618,14 +1625,14 @@ function flushIfFailed(exitCode, chunks) {
1618
1625
 
1619
1626
  // src/commands/verify/run/runAllEntries.ts
1620
1627
  function runEntry(entry, onComplete) {
1621
- return new Promise((resolve3) => {
1628
+ return new Promise((resolve5) => {
1622
1629
  const child = spawnCommand(entry.fullCommand, entry.cwd, entry.env);
1623
1630
  const chunks = collectOutput(child);
1624
1631
  child.on("close", (code) => {
1625
1632
  const exitCode = code ?? 1;
1626
1633
  flushIfFailed(exitCode, chunks);
1627
1634
  onComplete?.(exitCode);
1628
- resolve3({ script: entry.name, code: exitCode });
1635
+ resolve5({ script: entry.name, code: exitCode });
1629
1636
  });
1630
1637
  });
1631
1638
  }
@@ -1770,7 +1777,7 @@ async function newCli() {
1770
1777
 
1771
1778
  // src/commands/new/registerNew/newProject.ts
1772
1779
  import { execSync as execSync12 } from "child_process";
1773
- import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
1780
+ import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
1774
1781
 
1775
1782
  // src/commands/deploy/init/index.ts
1776
1783
  import { execSync as execSync11 } from "child_process";
@@ -1778,33 +1785,33 @@ import chalk21 from "chalk";
1778
1785
  import enquirer3 from "enquirer";
1779
1786
 
1780
1787
  // src/commands/deploy/init/updateWorkflow.ts
1781
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
1788
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
1782
1789
  import { dirname as dirname10, join as join7 } from "path";
1783
1790
  import { fileURLToPath as fileURLToPath2 } from "url";
1784
1791
  import chalk20 from "chalk";
1785
1792
  var WORKFLOW_PATH = ".github/workflows/build.yml";
1786
1793
  var __dirname3 = dirname10(fileURLToPath2(import.meta.url));
1787
1794
  function getExistingSiteId() {
1788
- if (!existsSync9(WORKFLOW_PATH)) {
1795
+ if (!existsSync10(WORKFLOW_PATH)) {
1789
1796
  return null;
1790
1797
  }
1791
- const content = readFileSync7(WORKFLOW_PATH, "utf-8");
1798
+ const content = readFileSync8(WORKFLOW_PATH, "utf-8");
1792
1799
  const match = content.match(/-s\s+([a-f0-9-]{36})/);
1793
1800
  return match ? match[1] : null;
1794
1801
  }
1795
1802
  function getTemplateContent(siteId) {
1796
1803
  const templatePath = join7(__dirname3, "commands/deploy/build.yml");
1797
- const template = readFileSync7(templatePath, "utf-8");
1804
+ const template = readFileSync8(templatePath, "utf-8");
1798
1805
  return template.replace("{{NETLIFY_SITE_ID}}", siteId);
1799
1806
  }
1800
1807
  async function updateWorkflow(siteId) {
1801
1808
  const newContent = getTemplateContent(siteId);
1802
1809
  const workflowDir = ".github/workflows";
1803
- if (!existsSync9(workflowDir)) {
1810
+ if (!existsSync10(workflowDir)) {
1804
1811
  mkdirSync3(workflowDir, { recursive: true });
1805
1812
  }
1806
- if (existsSync9(WORKFLOW_PATH)) {
1807
- const oldContent = readFileSync7(WORKFLOW_PATH, "utf-8");
1813
+ if (existsSync10(WORKFLOW_PATH)) {
1814
+ const oldContent = readFileSync8(WORKFLOW_PATH, "utf-8");
1808
1815
  if (oldContent === newContent) {
1809
1816
  console.log(chalk20.green("build.yml is already up to date"));
1810
1817
  return;
@@ -1898,11 +1905,11 @@ async function newProject() {
1898
1905
  }
1899
1906
  function addViteBaseConfig() {
1900
1907
  const viteConfigPath = "vite.config.ts";
1901
- if (!existsSync10(viteConfigPath)) {
1908
+ if (!existsSync11(viteConfigPath)) {
1902
1909
  console.log("No vite.config.ts found, skipping base config");
1903
1910
  return;
1904
1911
  }
1905
- const content = readFileSync8(viteConfigPath, "utf-8");
1912
+ const content = readFileSync9(viteConfigPath, "utf-8");
1906
1913
  if (content.includes("base:")) {
1907
1914
  console.log("vite.config.ts already has base config");
1908
1915
  return;
@@ -2045,11 +2052,11 @@ async function notify() {
2045
2052
  }
2046
2053
 
2047
2054
  // src/commands/backlog/add/index.ts
2048
- import { existsSync as existsSync12 } from "fs";
2055
+ import { existsSync as existsSync13 } from "fs";
2049
2056
  import chalk23 from "chalk";
2050
2057
 
2051
2058
  // src/commands/backlog/shared.ts
2052
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync10 } from "fs";
2059
+ import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
2053
2060
  import { join as join8 } from "path";
2054
2061
  import chalk22 from "chalk";
2055
2062
  import { parse as parseYaml2, stringify as stringifyYaml3 } from "yaml";
@@ -2074,11 +2081,11 @@ function getBacklogPath() {
2074
2081
  }
2075
2082
  function loadBacklog() {
2076
2083
  const backlogPath = getBacklogPath();
2077
- if (!existsSync11(backlogPath)) {
2084
+ if (!existsSync12(backlogPath)) {
2078
2085
  return [];
2079
2086
  }
2080
2087
  try {
2081
- const content = readFileSync9(backlogPath, "utf-8");
2088
+ const content = readFileSync10(backlogPath, "utf-8");
2082
2089
  const raw = parseYaml2(content) || [];
2083
2090
  return backlogFileSchema.parse(raw);
2084
2091
  } catch {
@@ -2093,7 +2100,7 @@ function findItem(items, id) {
2093
2100
  return items.find((item) => item.id === id);
2094
2101
  }
2095
2102
  function loadAndFindItem(id) {
2096
- if (!existsSync11(getBacklogPath())) {
2103
+ if (!existsSync12(getBacklogPath())) {
2097
2104
  console.log(
2098
2105
  chalk22.yellow(
2099
2106
  "No backlog found. Run 'assist backlog init' to create one."
@@ -2129,7 +2136,7 @@ function getNextId(items) {
2129
2136
 
2130
2137
  // src/commands/backlog/add/shared.ts
2131
2138
  import { spawnSync } from "child_process";
2132
- import { mkdtempSync, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
2139
+ import { mkdtempSync, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "fs";
2133
2140
  import { tmpdir } from "os";
2134
2141
  import { join as join9 } from "path";
2135
2142
  import enquirer4 from "enquirer";
@@ -2179,7 +2186,7 @@ function openEditor() {
2179
2186
  unlinkSync2(filePath);
2180
2187
  return void 0;
2181
2188
  }
2182
- const content = readFileSync10(filePath, "utf-8").trim();
2189
+ const content = readFileSync11(filePath, "utf-8").trim();
2183
2190
  unlinkSync2(filePath);
2184
2191
  return content || void 0;
2185
2192
  }
@@ -2200,7 +2207,7 @@ async function promptAcceptanceCriteria() {
2200
2207
  // src/commands/backlog/add/index.ts
2201
2208
  async function add() {
2202
2209
  const backlogPath = getBacklogPath();
2203
- if (!existsSync12(backlogPath)) {
2210
+ if (!existsSync13(backlogPath)) {
2204
2211
  console.log(
2205
2212
  chalk23.yellow(
2206
2213
  "No backlog found. Run 'assist backlog init' to create one."
@@ -2245,11 +2252,11 @@ async function done(id) {
2245
2252
  }
2246
2253
 
2247
2254
  // src/commands/backlog/init/index.ts
2248
- import { existsSync as existsSync13 } from "fs";
2255
+ import { existsSync as existsSync14 } from "fs";
2249
2256
  import chalk26 from "chalk";
2250
2257
  async function init6() {
2251
2258
  const backlogPath = getBacklogPath();
2252
- if (existsSync13(backlogPath)) {
2259
+ if (existsSync14(backlogPath)) {
2253
2260
  console.log(chalk26.yellow("assist.backlog.yml already exists."));
2254
2261
  return;
2255
2262
  }
@@ -2258,7 +2265,7 @@ async function init6() {
2258
2265
  }
2259
2266
 
2260
2267
  // src/commands/backlog/list/index.ts
2261
- import { existsSync as existsSync14 } from "fs";
2268
+ import { existsSync as existsSync15 } from "fs";
2262
2269
  import chalk27 from "chalk";
2263
2270
  function statusIcon(status2) {
2264
2271
  switch (status2) {
@@ -2297,7 +2304,7 @@ function filterItems(items, options2) {
2297
2304
  }
2298
2305
  async function list2(options2) {
2299
2306
  const backlogPath = getBacklogPath();
2300
- if (!existsSync14(backlogPath)) {
2307
+ if (!existsSync15(backlogPath)) {
2301
2308
  console.log(
2302
2309
  chalk27.yellow(
2303
2310
  "No backlog found. Run 'assist backlog init' to create one."
@@ -2335,7 +2342,7 @@ import { createServer } from "http";
2335
2342
  import chalk29 from "chalk";
2336
2343
 
2337
2344
  // src/commands/backlog/web/handleRequest.ts
2338
- import { readFileSync as readFileSync11 } from "fs";
2345
+ import { readFileSync as readFileSync12 } from "fs";
2339
2346
  import { dirname as dirname11, join as join10 } from "path";
2340
2347
  import { fileURLToPath as fileURLToPath3 } from "url";
2341
2348
 
@@ -2368,12 +2375,12 @@ function respondJson(res, status2, data) {
2368
2375
  res.end(JSON.stringify(data));
2369
2376
  }
2370
2377
  function readBody(req) {
2371
- return new Promise((resolve3, reject) => {
2378
+ return new Promise((resolve5, reject) => {
2372
2379
  let body = "";
2373
2380
  req.on("data", (chunk) => {
2374
2381
  body += chunk.toString();
2375
2382
  });
2376
- req.on("end", () => resolve3(body));
2383
+ req.on("end", () => resolve5(body));
2377
2384
  req.on("error", reject);
2378
2385
  });
2379
2386
  }
@@ -2438,7 +2445,7 @@ var __dirname4 = dirname11(fileURLToPath3(import.meta.url));
2438
2445
  var bundleCache;
2439
2446
  function serveBundle(_req, res) {
2440
2447
  if (!bundleCache) {
2441
- bundleCache = readFileSync11(
2448
+ bundleCache = readFileSync12(
2442
2449
  join10(__dirname4, "commands/backlog/web/bundle.js"),
2443
2450
  "utf-8"
2444
2451
  );
@@ -2510,466 +2517,221 @@ function registerBacklog(program2) {
2510
2517
  backlogCommand.command("web").description("Start a web view of the backlog").option("-p, --port <number>", "Port to listen on", "3000").action(web);
2511
2518
  }
2512
2519
 
2513
- // src/commands/cliDiscover/index.ts
2514
- import { existsSync as existsSync16, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync13 } from "fs";
2515
- import { homedir as homedir3 } from "os";
2516
- import { join as join12 } from "path";
2517
-
2518
- // src/commands/cliDiscover/assertCliExists.ts
2519
- import { execSync as execSync13 } from "child_process";
2520
- function assertCliExists(cli) {
2521
- const binary = cli.split(/\s+/)[0];
2522
- const opts = {
2523
- encoding: "utf-8",
2524
- stdio: ["ignore", "pipe", "pipe"]
2525
- };
2526
- try {
2527
- execSync13(`command -v ${binary}`, opts);
2528
- } catch {
2529
- try {
2530
- execSync13(`where ${binary}`, opts);
2531
- } catch {
2532
- console.error(`CLI "${cli}" not found in PATH`);
2533
- process.exit(1);
2520
+ // src/shared/tokenize.ts
2521
+ function tokenize(command) {
2522
+ const tokens = [];
2523
+ let current = "";
2524
+ let inSingle = false;
2525
+ let inDouble = false;
2526
+ for (let i = 0; i < command.length; i++) {
2527
+ const ch = command[i];
2528
+ if (ch === "'" && !inDouble) {
2529
+ inSingle = !inSingle;
2530
+ current += ch;
2531
+ } else if (ch === '"' && !inSingle) {
2532
+ inDouble = !inDouble;
2533
+ current += ch;
2534
+ } else if (ch === "\\" && inDouble && i + 1 < command.length) {
2535
+ current += ch + command[++i];
2536
+ } else if (/\s/.test(ch) && !inSingle && !inDouble) {
2537
+ if (current) {
2538
+ tokens.push(current);
2539
+ current = "";
2540
+ }
2541
+ } else {
2542
+ current += ch;
2534
2543
  }
2535
2544
  }
2545
+ if (current) tokens.push(current);
2546
+ return tokens;
2536
2547
  }
2537
2548
 
2538
- // src/commands/cliDiscover/colorize.ts
2539
- import chalk30 from "chalk";
2540
- function colorize(plainOutput) {
2541
- return plainOutput.split("\n").map((line) => {
2542
- if (line.startsWith(" R ")) return chalk30.green(line);
2543
- if (line.startsWith(" W ")) return chalk30.red(line);
2544
- return line;
2545
- }).join("\n");
2549
+ // src/shared/isGhApiRead.ts
2550
+ var READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
2551
+ var BODY_FLAGS = /* @__PURE__ */ new Set(["-f", "-F", "--field", "--raw-field", "--input"]);
2552
+ var FIELD_FLAGS = /* @__PURE__ */ new Set(["-f", "--field", "-F"]);
2553
+ function isGhApiRead(command) {
2554
+ const tokens = tokenize(command);
2555
+ if (tokens.length < 2 || tokens[0] !== "gh" || tokens[1] !== "api") {
2556
+ return false;
2557
+ }
2558
+ const args = tokens.slice(2);
2559
+ if (args.includes("graphql")) {
2560
+ return isGraphqlRead(args);
2561
+ }
2562
+ const method = extractMethod(args);
2563
+ if (method) return READ_METHODS.has(method.toUpperCase());
2564
+ if (args.some((t) => BODY_FLAGS.has(t))) return false;
2565
+ return true;
2546
2566
  }
2547
-
2548
- // src/lib/isClaudeCode.ts
2549
- function isClaudeCode() {
2550
- return process.env.CLAUDECODE !== void 0;
2567
+ function extractMethod(args) {
2568
+ for (let i = 0; i < args.length; i++) {
2569
+ const arg = args[i];
2570
+ if (arg.startsWith("--method=")) return arg.slice("--method=".length);
2571
+ if (arg.startsWith("-X=")) return arg.slice("-X=".length);
2572
+ if ((arg === "--method" || arg === "-X") && i + 1 < args.length) {
2573
+ return args[i + 1];
2574
+ }
2575
+ }
2576
+ return void 0;
2551
2577
  }
2552
-
2553
- // src/commands/cliDiscover/mapAsync.ts
2554
- async function mapAsync(items, concurrency, fn) {
2555
- const results = new Array(items.length);
2556
- let next2 = 0;
2557
- async function worker() {
2558
- while (next2 < items.length) {
2559
- const idx = next2++;
2560
- results[idx] = await fn(items[idx]);
2578
+ function isGraphqlRead(args) {
2579
+ const method = extractMethod(args);
2580
+ if (method) return READ_METHODS.has(method.toUpperCase());
2581
+ const queryBody = extractGraphqlQuery(args);
2582
+ if (!queryBody) return false;
2583
+ const trimmed = queryBody.replace(/^['"]|['"]$/g, "").trimStart();
2584
+ if (trimmed.startsWith("mutation")) return false;
2585
+ if (trimmed.startsWith("query") || trimmed.startsWith("{")) return true;
2586
+ return false;
2587
+ }
2588
+ function extractGraphqlQuery(args) {
2589
+ for (let i = 0; i < args.length; i++) {
2590
+ if (FIELD_FLAGS.has(args[i]) && i + 1 < args.length) {
2591
+ const match = args[i + 1].match(/^query=(.*)/s);
2592
+ if (match) return match[1];
2561
2593
  }
2562
2594
  }
2563
- const workers = Array.from(
2564
- { length: Math.min(concurrency, items.length) },
2565
- () => worker()
2566
- );
2567
- await Promise.all(workers);
2568
- return results;
2595
+ return void 0;
2569
2596
  }
2570
2597
 
2571
- // src/commands/cliDiscover/parseCommands.ts
2572
- var COMMAND_SECTION_RE = /^((?:core |general |available |additional |other |management |targeted |alias |github actions )?(?:commands|subgroups)):?$/i;
2573
- function isSkippable(name) {
2574
- return name.startsWith("-") || name.startsWith("<") || name.startsWith("[");
2598
+ // src/shared/loadCliReads.ts
2599
+ import { execFileSync } from "child_process";
2600
+ import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync12 } from "fs";
2601
+ import { dirname as dirname12, resolve as resolve2 } from "path";
2602
+ import { fileURLToPath as fileURLToPath4 } from "url";
2603
+ var __filename2 = fileURLToPath4(import.meta.url);
2604
+ var __dirname5 = dirname12(__filename2);
2605
+ function getCliReadsPath() {
2606
+ return resolve2(__dirname5, "..", "assist.cli-reads");
2607
+ }
2608
+ function loadCliReads() {
2609
+ const path31 = getCliReadsPath();
2610
+ if (!existsSync16(path31)) return [];
2611
+ return readFileSync13(path31, "utf-8").split("\n").filter((line) => line.trim() !== "");
2612
+ }
2613
+ function saveCliReads(commands) {
2614
+ writeFileSync12(getCliReadsPath(), `${commands.join("\n")}
2615
+ `);
2575
2616
  }
2576
- function parseCommandLine(trimmed) {
2577
- const azMatch = trimmed.match(/^(\S+)\s+(?:\[.*?]\s+)?:\s*(.+)/);
2578
- if (azMatch && !isSkippable(azMatch[1])) {
2579
- return { name: azMatch[1], description: azMatch[2].trim() };
2617
+ function findCliRead(command) {
2618
+ const filePath = getCliReadsPath();
2619
+ if (!existsSync16(filePath)) return void 0;
2620
+ const words = command.split(/\s+/);
2621
+ if (words.length < 2) return void 0;
2622
+ const prefix2 = `${words[0]} ${words[1]}`;
2623
+ let candidates;
2624
+ try {
2625
+ const output = execFileSync("grep", ["-E", `^${prefix2}( |$)`, filePath], {
2626
+ encoding: "utf-8"
2627
+ });
2628
+ candidates = output.split("\n").filter((l) => l !== "");
2629
+ } catch {
2630
+ return void 0;
2580
2631
  }
2581
- const colonMatch = trimmed.match(/^(\S+?):\s{2,}(.+)/);
2582
- if (colonMatch && !isSkippable(colonMatch[1])) {
2583
- return { name: colonMatch[1], description: colonMatch[2].trim() };
2632
+ return candidates.sort((a, b) => b.length - a.length).find((rc) => command === rc || command.startsWith(`${rc} `));
2633
+ }
2634
+
2635
+ // src/commands/cliHook/index.ts
2636
+ async function cliHook() {
2637
+ const inputData = await readStdin();
2638
+ let data;
2639
+ try {
2640
+ data = JSON.parse(inputData);
2641
+ } catch {
2642
+ return;
2584
2643
  }
2585
- const spaceMatch = trimmed.match(/^(\S+)(?:,\s*\S+)?\s{2,}(.+)/);
2586
- if (spaceMatch && !isSkippable(spaceMatch[1])) {
2587
- return { name: spaceMatch[1], description: spaceMatch[2].trim() };
2644
+ if (data.tool_name !== "Bash" || !data.tool_input?.command) {
2645
+ return;
2588
2646
  }
2589
- if (/^\S+$/.test(trimmed) && !isSkippable(trimmed)) {
2590
- return { name: trimmed, description: "" };
2647
+ const command = data.tool_input.command.trim();
2648
+ const matched = findCliRead(command);
2649
+ if (matched) {
2650
+ console.log(
2651
+ JSON.stringify({
2652
+ hookSpecificOutput: {
2653
+ hookEventName: "PreToolUse",
2654
+ permissionDecision: "allow",
2655
+ permissionDecisionReason: `Read-only CLI command: ${matched}`
2656
+ }
2657
+ })
2658
+ );
2659
+ return;
2591
2660
  }
2592
- return void 0;
2593
- }
2594
- function parseCommands(helpText) {
2595
- const commands = [];
2596
- let inCommandSection = false;
2597
- for (const line of helpText.split("\n")) {
2598
- const trimmed = line.trim();
2599
- if (COMMAND_SECTION_RE.test(trimmed)) {
2600
- inCommandSection = true;
2601
- continue;
2602
- }
2603
- if (inCommandSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
2604
- inCommandSection = false;
2605
- continue;
2606
- }
2607
- if (!inCommandSection || !trimmed) continue;
2608
- if (trimmed.startsWith("-") || trimmed.startsWith("=")) continue;
2609
- const parsed = parseCommandLine(trimmed);
2610
- if (parsed) commands.push(parsed);
2661
+ if (isGhApiRead(command)) {
2662
+ console.log(
2663
+ JSON.stringify({
2664
+ hookSpecificOutput: {
2665
+ hookEventName: "PreToolUse",
2666
+ permissionDecision: "allow",
2667
+ permissionDecisionReason: "Read-only gh api command"
2668
+ }
2669
+ })
2670
+ );
2611
2671
  }
2612
- return commands;
2613
- }
2614
- var COMMAND_SECTION_MULTILINE_RE = new RegExp(
2615
- COMMAND_SECTION_RE.source,
2616
- "im"
2617
- );
2618
- function hasSubcommands(helpText) {
2619
- return COMMAND_SECTION_MULTILINE_RE.test(helpText);
2620
2672
  }
2621
2673
 
2622
- // src/commands/cliDiscover/runHelp.ts
2623
- import { exec as exec2 } from "child_process";
2624
- function runHelp(args) {
2625
- return new Promise((resolve3) => {
2626
- exec2(
2627
- `${args.join(" ")} --help`,
2628
- { encoding: "utf-8", timeout: 3e4 },
2629
- (_err, stdout, stderr) => {
2630
- resolve3(stdout || stderr || "");
2631
- }
2632
- );
2674
+ // src/commands/registerCliHook.ts
2675
+ function registerCliHook(program2) {
2676
+ program2.command("cli-hook").description("PreToolUse hook for auto-approving read-only CLI commands").action(() => {
2677
+ cliHook();
2633
2678
  });
2634
2679
  }
2635
2680
 
2636
- // src/commands/cliDiscover/discoverAll.ts
2637
- var SAFETY_DEPTH = 10;
2638
- var CONCURRENCY = 8;
2639
- var interactive = !isClaudeCode();
2640
- function showProgress(p, label2) {
2641
- if (!interactive) return;
2642
- const pct = Math.round(p.done / p.total * 100);
2643
- process.stderr.write(`\r\x1B[K[${pct}%] Scanning ${label2}...`);
2681
+ // src/commands/complexity/analyze.ts
2682
+ import chalk35 from "chalk";
2683
+
2684
+ // src/commands/complexity/cyclomatic.ts
2685
+ import chalk31 from "chalk";
2686
+
2687
+ // src/commands/complexity/shared/index.ts
2688
+ import fs11 from "fs";
2689
+ import path16 from "path";
2690
+ import chalk30 from "chalk";
2691
+ import ts5 from "typescript";
2692
+
2693
+ // src/commands/complexity/findSourceFiles.ts
2694
+ import fs10 from "fs";
2695
+ import path15 from "path";
2696
+ import { minimatch as minimatch3 } from "minimatch";
2697
+ function applyIgnoreGlobs(files) {
2698
+ const { complexity } = loadConfig();
2699
+ return files.filter(
2700
+ (f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
2701
+ );
2644
2702
  }
2645
- async function resolveCommand(cli, path31, description, depth, p) {
2646
- showProgress(p, path31.join(" "));
2647
- const subHelp = await runHelp([cli, ...path31]);
2648
- if (!subHelp || !hasSubcommands(subHelp)) {
2649
- return [{ path: path31, description }];
2703
+ function walk(dir, results) {
2704
+ if (!fs10.existsSync(dir)) {
2705
+ return;
2706
+ }
2707
+ const extensions = [".ts", ".tsx"];
2708
+ const entries = fs10.readdirSync(dir, { withFileTypes: true });
2709
+ for (const entry of entries) {
2710
+ const fullPath = path15.join(dir, entry.name);
2711
+ if (entry.isDirectory()) {
2712
+ if (entry.name !== "node_modules" && entry.name !== ".git") {
2713
+ walk(fullPath, results);
2714
+ }
2715
+ } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
2716
+ results.push(fullPath);
2717
+ }
2650
2718
  }
2651
- const children = await discoverAt(cli, path31, depth + 1, p);
2652
- return children.length > 0 ? children : [{ path: path31, description }];
2653
2719
  }
2654
- async function discoverAt(cli, parentPath, depth, p) {
2655
- if (depth > SAFETY_DEPTH) return [];
2656
- const helpText = await runHelp([cli, ...parentPath]);
2657
- if (!helpText) return [];
2658
- const cmds = parseCommands(helpText);
2659
- const results = await mapAsync(
2660
- cmds,
2661
- CONCURRENCY,
2662
- (cmd) => resolveCommand(cli, [...parentPath, cmd.name], cmd.description, depth, p)
2663
- );
2664
- return results.flat();
2665
- }
2666
- async function discoverAll(cli) {
2667
- const topLevel = parseCommands(await runHelp([cli]));
2668
- const p = { done: 0, total: topLevel.length };
2669
- const results = await mapAsync(topLevel, CONCURRENCY, async (cmd) => {
2670
- showProgress(p, cmd.name);
2671
- const resolved = await resolveCommand(
2672
- cli,
2673
- [cmd.name],
2674
- cmd.description,
2675
- 1,
2676
- p
2677
- );
2678
- p.done++;
2679
- showProgress(p, cmd.name);
2680
- return resolved;
2681
- });
2682
- if (interactive) process.stderr.write("\r\x1B[K");
2683
- return results.flat();
2684
- }
2685
-
2686
- // src/commands/cliDiscover/classifyVerb.ts
2687
- var READ_VERBS = /* @__PURE__ */ new Set([
2688
- "list",
2689
- "show",
2690
- "view",
2691
- "export",
2692
- "get",
2693
- "diff",
2694
- "status",
2695
- "search",
2696
- "checks",
2697
- "describe",
2698
- "inspect",
2699
- "logs",
2700
- "cat",
2701
- "top",
2702
- "explain",
2703
- "exists",
2704
- "browse",
2705
- "watch"
2706
- ]);
2707
- var WRITE_VERBS = /* @__PURE__ */ new Set([
2708
- "create",
2709
- "delete",
2710
- "import",
2711
- "set",
2712
- "update",
2713
- "merge",
2714
- "close",
2715
- "reopen",
2716
- "edit",
2717
- "apply",
2718
- "patch",
2719
- "drain",
2720
- "cordon",
2721
- "taint",
2722
- "push",
2723
- "deploy",
2724
- "add",
2725
- "remove",
2726
- "assign",
2727
- "unassign",
2728
- "lock",
2729
- "unlock",
2730
- "start",
2731
- "stop",
2732
- "restart",
2733
- "enable",
2734
- "disable",
2735
- "revoke",
2736
- "rotate"
2737
- ]);
2738
- function classifyVerb(verb) {
2739
- if (READ_VERBS.has(verb)) return "r";
2740
- if (WRITE_VERBS.has(verb)) return "w";
2741
- return "?";
2742
- }
2743
-
2744
- // src/commands/cliDiscover/formatHuman.ts
2745
- function prefix(kind) {
2746
- if (kind === "r") return " R ";
2747
- if (kind === "w") return " W ";
2748
- return " ? ";
2749
- }
2750
- function formatHuman(cli, commands) {
2751
- const sorted = [...commands].sort(
2752
- (a, b) => a.path.join(" ").localeCompare(b.path.join(" "))
2753
- );
2754
- const lines = [`Discovered ${commands.length} commands for "${cli}":
2755
- `];
2756
- for (const cmd of sorted) {
2757
- const verb = cmd.path[cmd.path.length - 1];
2758
- const full = `${cli} ${cmd.path.join(" ")}`;
2759
- const text = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
2760
- lines.push(`${prefix(classifyVerb(verb))}${text}`);
2761
- }
2762
- return lines.join("\n");
2763
- }
2764
-
2765
- // src/commands/cliDiscover/parseCached.ts
2766
- function parseCached(cli, cached) {
2767
- const prefix2 = `${cli} `;
2768
- const commands = [];
2769
- for (const line of cached.split("\n")) {
2770
- const trimmed = line.replace(/^ [RW?] {2}/, "").trim();
2771
- if (!trimmed.startsWith(prefix2)) continue;
2772
- const rest = trimmed.slice(prefix2.length);
2773
- const dashIdx = rest.indexOf(" \u2014 ");
2774
- const pathStr = dashIdx >= 0 ? rest.slice(0, dashIdx) : rest;
2775
- const description = dashIdx >= 0 ? rest.slice(dashIdx + 3) : "";
2776
- commands.push({ path: pathStr.split(" "), description });
2777
- }
2778
- return commands;
2779
- }
2780
-
2781
- // src/commands/cliDiscover/updateSettings.ts
2782
- import { existsSync as existsSync15, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
2783
- import { join as join11 } from "path";
2784
- function settingsPath() {
2785
- return join11(process.cwd(), "claude", "settings.json");
2786
- }
2787
- function updateSettings(cli, commands) {
2788
- const path31 = settingsPath();
2789
- if (!existsSync15(path31)) return;
2790
- const settings = JSON.parse(readFileSync12(path31, "utf-8"));
2791
- const allow = settings.permissions?.allow ?? [];
2792
- const readCommands = commands.filter(
2793
- (cmd) => classifyVerb(cmd.path[cmd.path.length - 1]) === "r"
2794
- );
2795
- const newEntries = readCommands.map(
2796
- (cmd) => `Bash(${cli} ${cmd.path.join(" ")}:*)`
2797
- );
2798
- const existing = new Set(allow);
2799
- const added = newEntries.filter((e) => !existing.has(e));
2800
- if (added.length === 0) return;
2801
- settings.permissions = settings.permissions ?? {};
2802
- settings.permissions.allow = [...allow, ...added];
2803
- writeFileSync12(path31, `${JSON.stringify(settings, null, " ")}
2804
- `);
2805
- }
2806
-
2807
- // src/commands/cliDiscover/index.ts
2808
- function logPath(cli) {
2809
- const safeName = cli.replace(/\s+/g, "-");
2810
- return join12(homedir3(), ".assist", `cli-discover-${safeName}.log`);
2811
- }
2812
- function readCache(cli) {
2813
- const path31 = logPath(cli);
2814
- if (!existsSync16(path31)) return void 0;
2815
- return readFileSync13(path31, "utf-8");
2816
- }
2817
- function writeCache(cli, output) {
2818
- const dir = join12(homedir3(), ".assist");
2819
- mkdirSync4(dir, { recursive: true });
2820
- writeFileSync13(logPath(cli), output);
2821
- }
2822
- async function cliDiscover(cli, options2 = { noCache: false }) {
2823
- if (!cli) {
2824
- console.error("Usage: assist cli-discover <cli>");
2825
- process.exit(1);
2826
- }
2827
- if (!options2.noCache) {
2828
- const cached = readCache(cli);
2829
- if (cached) {
2830
- console.log(colorize(cached));
2831
- updateSettings(cli, parseCached(cli, cached));
2832
- return;
2833
- }
2834
- }
2835
- assertCliExists(cli);
2836
- const commands = await discoverAll(cli);
2837
- const output = formatHuman(cli, commands);
2838
- console.log(colorize(output));
2839
- writeCache(cli, output);
2840
- updateSettings(cli, commands);
2841
- }
2842
-
2843
- // src/commands/registerCliDiscover.ts
2844
- function registerCliDiscover(program2) {
2845
- program2.command("cli-discover").description("Discover a CLI's command tree via recursive --help parsing").argument(
2846
- "<cli...>",
2847
- "CLI binary and optional subcommand (e.g. gh, az, acli jira)"
2848
- ).option("--no-cache", "Force fresh discovery, ignoring cached results").action((cli, options2) => {
2849
- cliDiscover(cli.join(" "), { noCache: !options2.cache });
2850
- });
2851
- }
2852
-
2853
- // src/commands/cliHook/extractVerb.ts
2854
- var SHELL_OPERATORS = /* @__PURE__ */ new Set(["|", ">", ">>", ";", "&&", "||"]);
2855
- function isShellBreak(token) {
2856
- return token.startsWith("-") || SHELL_OPERATORS.has(token);
2857
- }
2858
- function looksLikeArgument(token) {
2859
- return /[/=.]/.test(token) || /^\d+$/.test(token);
2860
- }
2861
- function extractVerb(command, readVerbs) {
2862
- const tokens = command.split(/\s+/);
2863
- for (let i = 1; i < tokens.length; i++) {
2864
- if (isShellBreak(tokens[i])) break;
2865
- if (readVerbs.includes(tokens[i])) return tokens[i];
2866
- }
2867
- for (let i = tokens.length - 1; i >= 1; i--) {
2868
- const token = tokens[i];
2869
- if (isShellBreak(token) || looksLikeArgument(token)) continue;
2870
- return token;
2871
- }
2872
- return void 0;
2873
- }
2874
-
2875
- // src/commands/cliHook/index.ts
2876
- async function cliHook() {
2877
- const inputData = await readStdin();
2878
- let data;
2879
- try {
2880
- data = JSON.parse(inputData);
2881
- } catch {
2882
- return;
2883
- }
2884
- if (data.tool_name !== "Bash" || !data.tool_input?.command) {
2885
- return;
2886
- }
2887
- const command = data.tool_input.command.trim();
2888
- const config = loadConfig();
2889
- const cliReadVerbs = config.cliReadVerbs;
2890
- if (!cliReadVerbs) return;
2891
- const cliKeys = Object.keys(cliReadVerbs).sort((a, b) => b.length - a.length);
2892
- const cli = cliKeys.find(
2893
- (key) => command === key || command.startsWith(`${key} `)
2894
- );
2895
- if (!cli) return;
2896
- const readVerbs = cliReadVerbs[cli];
2897
- if (!readVerbs || readVerbs.length === 0) return;
2898
- const verb = extractVerb(command, readVerbs);
2899
- if (verb && readVerbs.includes(verb)) {
2900
- console.log(
2901
- JSON.stringify({
2902
- hookSpecificOutput: {
2903
- hookEventName: "PreToolUse",
2904
- permissionDecision: "allow",
2905
- permissionDecisionReason: `Read-only ${cli} command: ${verb}`
2906
- }
2907
- })
2908
- );
2909
- }
2910
- }
2911
-
2912
- // src/commands/registerCliHook.ts
2913
- function registerCliHook(program2) {
2914
- program2.command("cli-hook").description("PreToolUse hook for auto-approving read-only CLI commands").action(() => {
2915
- cliHook();
2916
- });
2917
- }
2918
-
2919
- // src/commands/complexity/analyze.ts
2920
- import chalk36 from "chalk";
2921
-
2922
- // src/commands/complexity/cyclomatic.ts
2923
- import chalk32 from "chalk";
2924
-
2925
- // src/commands/complexity/shared/index.ts
2926
- import fs11 from "fs";
2927
- import path16 from "path";
2928
- import chalk31 from "chalk";
2929
- import ts5 from "typescript";
2930
-
2931
- // src/commands/complexity/findSourceFiles.ts
2932
- import fs10 from "fs";
2933
- import path15 from "path";
2934
- import { minimatch as minimatch3 } from "minimatch";
2935
- function applyIgnoreGlobs(files) {
2936
- const { complexity } = loadConfig();
2937
- return files.filter(
2938
- (f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
2939
- );
2940
- }
2941
- function walk(dir, results) {
2942
- if (!fs10.existsSync(dir)) {
2943
- return;
2944
- }
2945
- const extensions = [".ts", ".tsx"];
2946
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
2947
- for (const entry of entries) {
2948
- const fullPath = path15.join(dir, entry.name);
2949
- if (entry.isDirectory()) {
2950
- if (entry.name !== "node_modules" && entry.name !== ".git") {
2951
- walk(fullPath, results);
2952
- }
2953
- } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
2954
- results.push(fullPath);
2955
- }
2956
- }
2957
- }
2958
- function findSourceFiles2(pattern2, baseDir = ".") {
2959
- const results = [];
2960
- if (pattern2.includes("*")) {
2961
- walk(baseDir, results);
2962
- return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
2963
- }
2964
- if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
2965
- return [pattern2];
2966
- }
2967
- if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isDirectory()) {
2968
- walk(pattern2, results);
2969
- return applyIgnoreGlobs(results);
2970
- }
2971
- walk(baseDir, results);
2972
- return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
2720
+ function findSourceFiles2(pattern2, baseDir = ".") {
2721
+ const results = [];
2722
+ if (pattern2.includes("*")) {
2723
+ walk(baseDir, results);
2724
+ return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
2725
+ }
2726
+ if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
2727
+ return [pattern2];
2728
+ }
2729
+ if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isDirectory()) {
2730
+ walk(pattern2, results);
2731
+ return applyIgnoreGlobs(results);
2732
+ }
2733
+ walk(baseDir, results);
2734
+ return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
2973
2735
  }
2974
2736
 
2975
2737
  // src/commands/complexity/shared/getNodeName.ts
@@ -3171,7 +2933,7 @@ function createSourceFromFile(filePath) {
3171
2933
  function withSourceFiles(pattern2, callback) {
3172
2934
  const files = findSourceFiles2(pattern2);
3173
2935
  if (files.length === 0) {
3174
- console.log(chalk31.yellow("No files found matching pattern"));
2936
+ console.log(chalk30.yellow("No files found matching pattern"));
3175
2937
  return void 0;
3176
2938
  }
3177
2939
  return callback(files);
@@ -3204,11 +2966,11 @@ async function cyclomatic(pattern2 = "**/*.ts", options2 = {}) {
3204
2966
  results.sort((a, b) => b.complexity - a.complexity);
3205
2967
  for (const { file, name, complexity } of results) {
3206
2968
  const exceedsThreshold = options2.threshold !== void 0 && complexity > options2.threshold;
3207
- const color = exceedsThreshold ? chalk32.red : chalk32.white;
3208
- console.log(`${color(`${file}:${name}`)} \u2192 ${chalk32.cyan(complexity)}`);
2969
+ const color = exceedsThreshold ? chalk31.red : chalk31.white;
2970
+ console.log(`${color(`${file}:${name}`)} \u2192 ${chalk31.cyan(complexity)}`);
3209
2971
  }
3210
2972
  console.log(
3211
- chalk32.dim(
2973
+ chalk31.dim(
3212
2974
  `
3213
2975
  Analyzed ${results.length} functions across ${files.length} files`
3214
2976
  )
@@ -3220,7 +2982,7 @@ Analyzed ${results.length} functions across ${files.length} files`
3220
2982
  }
3221
2983
 
3222
2984
  // src/commands/complexity/halstead.ts
3223
- import chalk33 from "chalk";
2985
+ import chalk32 from "chalk";
3224
2986
  async function halstead(pattern2 = "**/*.ts", options2 = {}) {
3225
2987
  withSourceFiles(pattern2, (files) => {
3226
2988
  const results = [];
@@ -3235,13 +2997,13 @@ async function halstead(pattern2 = "**/*.ts", options2 = {}) {
3235
2997
  results.sort((a, b) => b.metrics.effort - a.metrics.effort);
3236
2998
  for (const { file, name, metrics } of results) {
3237
2999
  const exceedsThreshold = options2.threshold !== void 0 && metrics.volume > options2.threshold;
3238
- const color = exceedsThreshold ? chalk33.red : chalk33.white;
3000
+ const color = exceedsThreshold ? chalk32.red : chalk32.white;
3239
3001
  console.log(
3240
- `${color(`${file}:${name}`)} \u2192 volume: ${chalk33.cyan(metrics.volume.toFixed(1))}, difficulty: ${chalk33.yellow(metrics.difficulty.toFixed(1))}, effort: ${chalk33.magenta(metrics.effort.toFixed(1))}`
3002
+ `${color(`${file}:${name}`)} \u2192 volume: ${chalk32.cyan(metrics.volume.toFixed(1))}, difficulty: ${chalk32.yellow(metrics.difficulty.toFixed(1))}, effort: ${chalk32.magenta(metrics.effort.toFixed(1))}`
3241
3003
  );
3242
3004
  }
3243
3005
  console.log(
3244
- chalk33.dim(
3006
+ chalk32.dim(
3245
3007
  `
3246
3008
  Analyzed ${results.length} functions across ${files.length} files`
3247
3009
  )
@@ -3256,28 +3018,28 @@ Analyzed ${results.length} functions across ${files.length} files`
3256
3018
  import fs12 from "fs";
3257
3019
 
3258
3020
  // src/commands/complexity/maintainability/displayMaintainabilityResults.ts
3259
- import chalk34 from "chalk";
3021
+ import chalk33 from "chalk";
3260
3022
  function displayMaintainabilityResults(results, threshold) {
3261
3023
  const filtered = threshold !== void 0 ? results.filter((r) => r.minMaintainability < threshold) : results;
3262
3024
  if (threshold !== void 0 && filtered.length === 0) {
3263
- console.log(chalk34.green("All files pass maintainability threshold"));
3025
+ console.log(chalk33.green("All files pass maintainability threshold"));
3264
3026
  } else {
3265
3027
  for (const { file, avgMaintainability, minMaintainability } of filtered) {
3266
- const color = threshold !== void 0 ? chalk34.red : chalk34.white;
3028
+ const color = threshold !== void 0 ? chalk33.red : chalk33.white;
3267
3029
  console.log(
3268
- `${color(file)} \u2192 avg: ${chalk34.cyan(avgMaintainability.toFixed(1))}, min: ${chalk34.yellow(minMaintainability.toFixed(1))}`
3030
+ `${color(file)} \u2192 avg: ${chalk33.cyan(avgMaintainability.toFixed(1))}, min: ${chalk33.yellow(minMaintainability.toFixed(1))}`
3269
3031
  );
3270
3032
  }
3271
3033
  }
3272
- console.log(chalk34.dim(`
3034
+ console.log(chalk33.dim(`
3273
3035
  Analyzed ${results.length} files`));
3274
3036
  if (filtered.length > 0 && threshold !== void 0) {
3275
3037
  console.error(
3276
- chalk34.red(
3038
+ chalk33.red(
3277
3039
  `
3278
3040
  Fail: ${filtered.length} file(s) below threshold ${threshold}. Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code.
3279
3041
 
3280
- \u26A0\uFE0F ${chalk34.bold("Diagnose and fix one file at a time")} \u2014 do not investigate or fix multiple files in parallel. Run 'assist complexity <file>' to see all metrics. For larger files, start by extracting responsibilities into smaller files.`
3042
+ \u26A0\uFE0F ${chalk33.bold("Diagnose and fix one file at a time")} \u2014 do not investigate or fix multiple files in parallel. Run 'assist complexity <file>' to see all metrics. For larger files, start by extracting responsibilities into smaller files.`
3281
3043
  )
3282
3044
  );
3283
3045
  process.exit(1);
@@ -3334,7 +3096,7 @@ async function maintainability(pattern2 = "**/*.ts", options2 = {}) {
3334
3096
 
3335
3097
  // src/commands/complexity/sloc.ts
3336
3098
  import fs13 from "fs";
3337
- import chalk35 from "chalk";
3099
+ import chalk34 from "chalk";
3338
3100
  async function sloc(pattern2 = "**/*.ts", options2 = {}) {
3339
3101
  withSourceFiles(pattern2, (files) => {
3340
3102
  const results = [];
@@ -3350,12 +3112,12 @@ async function sloc(pattern2 = "**/*.ts", options2 = {}) {
3350
3112
  results.sort((a, b) => b.lines - a.lines);
3351
3113
  for (const { file, lines } of results) {
3352
3114
  const exceedsThreshold = options2.threshold !== void 0 && lines > options2.threshold;
3353
- const color = exceedsThreshold ? chalk35.red : chalk35.white;
3354
- console.log(`${color(file)} \u2192 ${chalk35.cyan(lines)} lines`);
3115
+ const color = exceedsThreshold ? chalk34.red : chalk34.white;
3116
+ console.log(`${color(file)} \u2192 ${chalk34.cyan(lines)} lines`);
3355
3117
  }
3356
3118
  const total = results.reduce((sum, r) => sum + r.lines, 0);
3357
3119
  console.log(
3358
- chalk35.dim(`
3120
+ chalk34.dim(`
3359
3121
  Total: ${total} lines across ${files.length} files`)
3360
3122
  );
3361
3123
  if (hasViolation) {
@@ -3369,21 +3131,21 @@ async function analyze(pattern2) {
3369
3131
  const searchPattern = pattern2.includes("*") || pattern2.includes("/") ? pattern2 : `**/${pattern2}`;
3370
3132
  const files = findSourceFiles2(searchPattern);
3371
3133
  if (files.length === 0) {
3372
- console.log(chalk36.yellow("No files found matching pattern"));
3134
+ console.log(chalk35.yellow("No files found matching pattern"));
3373
3135
  return;
3374
3136
  }
3375
3137
  if (files.length === 1) {
3376
3138
  const file = files[0];
3377
- console.log(chalk36.bold.underline("SLOC"));
3139
+ console.log(chalk35.bold.underline("SLOC"));
3378
3140
  await sloc(file);
3379
3141
  console.log();
3380
- console.log(chalk36.bold.underline("Cyclomatic Complexity"));
3142
+ console.log(chalk35.bold.underline("Cyclomatic Complexity"));
3381
3143
  await cyclomatic(file);
3382
3144
  console.log();
3383
- console.log(chalk36.bold.underline("Halstead Metrics"));
3145
+ console.log(chalk35.bold.underline("Halstead Metrics"));
3384
3146
  await halstead(file);
3385
3147
  console.log();
3386
- console.log(chalk36.bold.underline("Maintainability Index"));
3148
+ console.log(chalk35.bold.underline("Maintainability Index"));
3387
3149
  await maintainability(file);
3388
3150
  return;
3389
3151
  }
@@ -3410,8 +3172,8 @@ function registerComplexity(program2) {
3410
3172
  }
3411
3173
 
3412
3174
  // src/commands/deploy/redirect.ts
3413
- import { existsSync as existsSync17, readFileSync as readFileSync14, writeFileSync as writeFileSync14 } from "fs";
3414
- import chalk37 from "chalk";
3175
+ import { existsSync as existsSync17, readFileSync as readFileSync14, writeFileSync as writeFileSync13 } from "fs";
3176
+ import chalk36 from "chalk";
3415
3177
  var TRAILING_SLASH_SCRIPT = ` <script>
3416
3178
  if (!window.location.pathname.endsWith('/')) {
3417
3179
  window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
@@ -3420,22 +3182,22 @@ var TRAILING_SLASH_SCRIPT = ` <script>
3420
3182
  function redirect() {
3421
3183
  const indexPath = "index.html";
3422
3184
  if (!existsSync17(indexPath)) {
3423
- console.log(chalk37.yellow("No index.html found"));
3185
+ console.log(chalk36.yellow("No index.html found"));
3424
3186
  return;
3425
3187
  }
3426
3188
  const content = readFileSync14(indexPath, "utf-8");
3427
3189
  if (content.includes("window.location.pathname.endsWith('/')")) {
3428
- console.log(chalk37.dim("Trailing slash script already present"));
3190
+ console.log(chalk36.dim("Trailing slash script already present"));
3429
3191
  return;
3430
3192
  }
3431
3193
  const headCloseIndex = content.indexOf("</head>");
3432
3194
  if (headCloseIndex === -1) {
3433
- console.log(chalk37.red("Could not find </head> tag in index.html"));
3195
+ console.log(chalk36.red("Could not find </head> tag in index.html"));
3434
3196
  return;
3435
3197
  }
3436
3198
  const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
3437
- writeFileSync14(indexPath, newContent);
3438
- console.log(chalk37.green("Added trailing slash redirect to index.html"));
3199
+ writeFileSync13(indexPath, newContent);
3200
+ console.log(chalk36.green("Added trailing slash redirect to index.html"));
3439
3201
  }
3440
3202
 
3441
3203
  // src/commands/registerDeploy.ts
@@ -3446,24 +3208,24 @@ function registerDeploy(program2) {
3446
3208
  }
3447
3209
 
3448
3210
  // src/commands/devlog/list/index.ts
3449
- import { execSync as execSync15 } from "child_process";
3211
+ import { execSync as execSync14 } from "child_process";
3450
3212
  import { basename as basename3 } from "path";
3451
3213
 
3452
3214
  // src/commands/devlog/shared.ts
3453
- import { execSync as execSync14 } from "child_process";
3454
- import chalk38 from "chalk";
3215
+ import { execSync as execSync13 } from "child_process";
3216
+ import chalk37 from "chalk";
3455
3217
 
3456
3218
  // src/commands/devlog/loadDevlogEntries.ts
3457
3219
  import { readdirSync, readFileSync as readFileSync15 } from "fs";
3458
- import { homedir as homedir4 } from "os";
3459
- import { join as join13 } from "path";
3460
- var DEVLOG_DIR = join13(homedir4(), "git/blog/src/content/devlog");
3220
+ import { homedir as homedir3 } from "os";
3221
+ import { join as join11 } from "path";
3222
+ var DEVLOG_DIR = join11(homedir3(), "git/blog/src/content/devlog");
3461
3223
  function loadDevlogEntries(repoName) {
3462
3224
  const entries = /* @__PURE__ */ new Map();
3463
3225
  try {
3464
3226
  const files = readdirSync(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
3465
3227
  for (const file of files) {
3466
- const content = readFileSync15(join13(DEVLOG_DIR, file), "utf-8");
3228
+ const content = readFileSync15(join11(DEVLOG_DIR, file), "utf-8");
3467
3229
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
3468
3230
  if (frontmatterMatch) {
3469
3231
  const frontmatter = frontmatterMatch[1];
@@ -3494,7 +3256,7 @@ function loadDevlogEntries(repoName) {
3494
3256
  // src/commands/devlog/shared.ts
3495
3257
  function getCommitFiles(hash) {
3496
3258
  try {
3497
- const output = execSync14(`git show --name-only --format="" ${hash}`, {
3259
+ const output = execSync13(`git show --name-only --format="" ${hash}`, {
3498
3260
  encoding: "utf-8"
3499
3261
  });
3500
3262
  return output.trim().split("\n").filter(Boolean);
@@ -3512,13 +3274,13 @@ function shouldIgnoreCommit(files, ignorePaths) {
3512
3274
  }
3513
3275
  function printCommitsWithFiles(commits, ignore2, verbose) {
3514
3276
  for (const commit2 of commits) {
3515
- console.log(` ${chalk38.yellow(commit2.hash)} ${commit2.message}`);
3277
+ console.log(` ${chalk37.yellow(commit2.hash)} ${commit2.message}`);
3516
3278
  if (verbose) {
3517
3279
  const visibleFiles = commit2.files.filter(
3518
3280
  (file) => !ignore2.some((p) => file.startsWith(p))
3519
3281
  );
3520
3282
  for (const file of visibleFiles) {
3521
- console.log(` ${chalk38.dim(file)}`);
3283
+ console.log(` ${chalk37.dim(file)}`);
3522
3284
  }
3523
3285
  }
3524
3286
  }
@@ -3543,15 +3305,15 @@ function parseGitLogCommits(output, ignore2, afterDate) {
3543
3305
  }
3544
3306
 
3545
3307
  // src/commands/devlog/list/printDateHeader.ts
3546
- import chalk39 from "chalk";
3308
+ import chalk38 from "chalk";
3547
3309
  function printDateHeader(date, isSkipped, entries) {
3548
3310
  if (isSkipped) {
3549
- console.log(`${chalk39.bold.blue(date)} ${chalk39.dim("skipped")}`);
3311
+ console.log(`${chalk38.bold.blue(date)} ${chalk38.dim("skipped")}`);
3550
3312
  } else if (entries && entries.length > 0) {
3551
- const entryInfo = entries.map((e) => `${chalk39.green(e.version)} ${e.title}`).join(" | ");
3552
- console.log(`${chalk39.bold.blue(date)} ${entryInfo}`);
3313
+ const entryInfo = entries.map((e) => `${chalk38.green(e.version)} ${e.title}`).join(" | ");
3314
+ console.log(`${chalk38.bold.blue(date)} ${entryInfo}`);
3553
3315
  } else {
3554
- console.log(`${chalk39.bold.blue(date)} ${chalk39.red("\u26A0 devlog missing")}`);
3316
+ console.log(`${chalk38.bold.blue(date)} ${chalk38.red("\u26A0 devlog missing")}`);
3555
3317
  }
3556
3318
  }
3557
3319
 
@@ -3565,7 +3327,7 @@ function list3(options2) {
3565
3327
  const devlogEntries = loadDevlogEntries(repoName);
3566
3328
  const reverseFlag = options2.reverse ? "--reverse " : "";
3567
3329
  const limitFlag = options2.reverse ? "" : "-n 500 ";
3568
- const output = execSync15(
3330
+ const output = execSync14(
3569
3331
  `git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
3570
3332
  { encoding: "utf-8" }
3571
3333
  );
@@ -3591,11 +3353,11 @@ function list3(options2) {
3591
3353
  }
3592
3354
 
3593
3355
  // src/commands/devlog/getLastVersionInfo.ts
3594
- import { execSync as execSync16 } from "child_process";
3356
+ import { execSync as execSync15 } from "child_process";
3595
3357
  import semver from "semver";
3596
3358
  function getVersionAtCommit(hash) {
3597
3359
  try {
3598
- const content = execSync16(`git show ${hash}:package.json`, {
3360
+ const content = execSync15(`git show ${hash}:package.json`, {
3599
3361
  encoding: "utf-8"
3600
3362
  });
3601
3363
  const pkg = JSON.parse(content);
@@ -3610,7 +3372,7 @@ function stripToMinor(version2) {
3610
3372
  }
3611
3373
  function getLastVersionInfoFromGit() {
3612
3374
  try {
3613
- const output = execSync16(
3375
+ const output = execSync15(
3614
3376
  "git log -1 --pretty=format:'%ad|%h' --date=short",
3615
3377
  {
3616
3378
  encoding: "utf-8"
@@ -3653,172 +3415,524 @@ function bumpVersion(version2, type) {
3653
3415
  }
3654
3416
 
3655
3417
  // src/commands/devlog/next/displayNextEntry/index.ts
3656
- import { execSync as execSync17 } from "child_process";
3657
- import chalk41 from "chalk";
3418
+ import { execSync as execSync16 } from "child_process";
3419
+ import chalk40 from "chalk";
3658
3420
 
3659
3421
  // src/commands/devlog/next/displayNextEntry/displayVersion.ts
3660
- import chalk40 from "chalk";
3422
+ import chalk39 from "chalk";
3661
3423
  function displayVersion(conventional, firstHash, patchVersion, minorVersion) {
3662
3424
  if (conventional && firstHash) {
3663
3425
  const version2 = getVersionAtCommit(firstHash);
3664
3426
  if (version2) {
3665
- console.log(`${chalk40.bold("version:")} ${stripToMinor(version2)}`);
3427
+ console.log(`${chalk39.bold("version:")} ${stripToMinor(version2)}`);
3666
3428
  } else {
3667
- console.log(`${chalk40.bold("version:")} ${chalk40.red("unknown")}`);
3429
+ console.log(`${chalk39.bold("version:")} ${chalk39.red("unknown")}`);
3668
3430
  }
3669
3431
  } else if (patchVersion && minorVersion) {
3670
3432
  console.log(
3671
- `${chalk40.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
3433
+ `${chalk39.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
3672
3434
  );
3673
3435
  } else {
3674
- console.log(`${chalk40.bold("version:")} v0.1 (initial)`);
3436
+ console.log(`${chalk39.bold("version:")} v0.1 (initial)`);
3437
+ }
3438
+ }
3439
+
3440
+ // src/commands/devlog/next/displayNextEntry/index.ts
3441
+ function computeVersions(lastInfo) {
3442
+ if (!lastInfo) return { patch: null, minor: null };
3443
+ return {
3444
+ patch: bumpVersion(lastInfo.version, "patch"),
3445
+ minor: bumpVersion(lastInfo.version, "minor")
3446
+ };
3447
+ }
3448
+ function findTargetDate(commitsByDate, skipDays) {
3449
+ return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
3450
+ }
3451
+ function fetchCommitsByDate(ignore2, lastDate) {
3452
+ const output = execSync16(
3453
+ "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
3454
+ { encoding: "utf-8" }
3455
+ );
3456
+ return parseGitLogCommits(output, ignore2, lastDate);
3457
+ }
3458
+ function printVersionInfo(config, lastInfo, firstHash) {
3459
+ const versions = computeVersions(lastInfo);
3460
+ displayVersion(
3461
+ !!config.commit?.conventional,
3462
+ firstHash,
3463
+ versions.patch,
3464
+ versions.minor
3465
+ );
3466
+ }
3467
+ function resolveIgnoreList(options2, config) {
3468
+ return options2.ignore ?? config.devlog?.ignore ?? [];
3469
+ }
3470
+ function resolveSkipDays(config) {
3471
+ return new Set(config.devlog?.skip?.days ?? []);
3472
+ }
3473
+ function getLastDate(lastInfo) {
3474
+ return lastInfo?.date ?? null;
3475
+ }
3476
+ function getCommitsForDate(commitsByDate, date) {
3477
+ return commitsByDate.get(date) ?? [];
3478
+ }
3479
+ function noCommitsMessage(hasLastInfo) {
3480
+ return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
3481
+ }
3482
+ function logName(repoName) {
3483
+ console.log(`${chalk40.bold("name:")} ${repoName}`);
3484
+ }
3485
+ function displayNextEntry(ctx, targetDate, commits) {
3486
+ logName(ctx.repoName);
3487
+ printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
3488
+ console.log(chalk40.bold.blue(targetDate));
3489
+ printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
3490
+ }
3491
+ function logNoCommits(lastInfo) {
3492
+ console.log(chalk40.dim(noCommitsMessage(!!lastInfo)));
3493
+ }
3494
+
3495
+ // src/commands/devlog/next/index.ts
3496
+ function resolveContextData(config, options2) {
3497
+ const repoName = getRepoName();
3498
+ const lastInfo = getLastVersionInfo(repoName, config);
3499
+ return { repoName, lastInfo, ignore: resolveIgnoreList(options2, config) };
3500
+ }
3501
+ function buildContext(options2) {
3502
+ const config = loadConfig();
3503
+ const data = resolveContextData(config, options2);
3504
+ return { config, ...data, verbose: options2.verbose ?? false };
3505
+ }
3506
+ function fetchNextCommits(ctx) {
3507
+ const commitsByDate = fetchCommitsByDate(
3508
+ ctx.ignore,
3509
+ getLastDate(ctx.lastInfo)
3510
+ );
3511
+ const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
3512
+ return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
3513
+ }
3514
+ function showResult(ctx, found) {
3515
+ if (!found) {
3516
+ logNoCommits(ctx.lastInfo);
3517
+ return;
3518
+ }
3519
+ displayNextEntry(ctx, found.targetDate, found.commits);
3520
+ }
3521
+ function next(options2) {
3522
+ const ctx = buildContext(options2);
3523
+ showResult(ctx, fetchNextCommits(ctx));
3524
+ }
3525
+
3526
+ // src/commands/devlog/skip.ts
3527
+ import chalk41 from "chalk";
3528
+ function skip(date) {
3529
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
3530
+ console.log(chalk41.red("Invalid date format. Use YYYY-MM-DD"));
3531
+ process.exit(1);
3532
+ }
3533
+ const config = loadProjectConfig();
3534
+ const devlog = config.devlog ?? {};
3535
+ const skip2 = devlog.skip ?? {};
3536
+ const skipDays = skip2.days ?? [];
3537
+ if (skipDays.includes(date)) {
3538
+ console.log(chalk41.yellow(`${date} is already in skip list`));
3539
+ return;
3540
+ }
3541
+ skipDays.push(date);
3542
+ skipDays.sort();
3543
+ skip2.days = skipDays;
3544
+ devlog.skip = skip2;
3545
+ config.devlog = devlog;
3546
+ saveConfig(config);
3547
+ console.log(chalk41.green(`Added ${date} to skip list`));
3548
+ }
3549
+
3550
+ // src/commands/devlog/version.ts
3551
+ import chalk42 from "chalk";
3552
+ function version() {
3553
+ const config = loadConfig();
3554
+ const name = getRepoName();
3555
+ const lastInfo = getLastVersionInfo(name, config);
3556
+ const lastVersion = lastInfo?.version ?? null;
3557
+ const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
3558
+ console.log(`${chalk42.bold("name:")} ${name}`);
3559
+ console.log(`${chalk42.bold("last:")} ${lastVersion ?? chalk42.dim("none")}`);
3560
+ console.log(`${chalk42.bold("next:")} ${nextVersion ?? chalk42.dim("none")}`);
3561
+ }
3562
+
3563
+ // src/commands/registerDevlog.ts
3564
+ function registerDevlog(program2) {
3565
+ const devlogCommand = program2.command("devlog").description("Development log utilities");
3566
+ devlogCommand.command("list").description("Group git commits by date").option(
3567
+ "--days <number>",
3568
+ "Number of days to show (default: 30)",
3569
+ Number.parseInt
3570
+ ).option("--since <date>", "Only show commits since this date (YYYY-MM-DD)").option("-r, --reverse", "Show earliest commits first").option("-v, --verbose", "Show file names for each commit").action(list3);
3571
+ devlogCommand.command("version").description("Show current repo name and version info").action(version);
3572
+ devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
3573
+ devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
3574
+ }
3575
+
3576
+ // src/commands/permitCliReads/index.ts
3577
+ import { existsSync as existsSync18, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
3578
+ import { homedir as homedir4 } from "os";
3579
+ import { join as join12 } from "path";
3580
+
3581
+ // src/shared/getInstallDir.ts
3582
+ import { execSync as execSync17 } from "child_process";
3583
+ import { dirname as dirname13, resolve as resolve3 } from "path";
3584
+ import { fileURLToPath as fileURLToPath5 } from "url";
3585
+ var __filename3 = fileURLToPath5(import.meta.url);
3586
+ var __dirname6 = dirname13(__filename3);
3587
+ function getInstallDir() {
3588
+ return resolve3(__dirname6, "..");
3589
+ }
3590
+ function isGitRepo(dir) {
3591
+ try {
3592
+ const result = execSync17("git rev-parse --show-toplevel", {
3593
+ cwd: dir,
3594
+ stdio: "pipe"
3595
+ }).toString().trim();
3596
+ return resolve3(result) === resolve3(dir);
3597
+ } catch {
3598
+ return false;
3599
+ }
3600
+ }
3601
+
3602
+ // src/commands/permitCliReads/assertCliExists.ts
3603
+ import { execSync as execSync18 } from "child_process";
3604
+ function assertCliExists(cli) {
3605
+ const binary = cli.split(/\s+/)[0];
3606
+ const opts = {
3607
+ encoding: "utf-8",
3608
+ stdio: ["ignore", "pipe", "pipe"]
3609
+ };
3610
+ try {
3611
+ execSync18(`command -v ${binary}`, opts);
3612
+ } catch {
3613
+ try {
3614
+ execSync18(`where ${binary}`, opts);
3615
+ } catch {
3616
+ console.error(`CLI "${cli}" not found in PATH`);
3617
+ process.exit(1);
3618
+ }
3675
3619
  }
3676
3620
  }
3677
3621
 
3678
- // src/commands/devlog/next/displayNextEntry/index.ts
3679
- function computeVersions(lastInfo) {
3680
- if (!lastInfo) return { patch: null, minor: null };
3681
- return {
3682
- patch: bumpVersion(lastInfo.version, "patch"),
3683
- minor: bumpVersion(lastInfo.version, "minor")
3684
- };
3685
- }
3686
- function findTargetDate(commitsByDate, skipDays) {
3687
- return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
3688
- }
3689
- function fetchCommitsByDate(ignore2, lastDate) {
3690
- const output = execSync17(
3691
- "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
3692
- { encoding: "utf-8" }
3693
- );
3694
- return parseGitLogCommits(output, ignore2, lastDate);
3622
+ // src/commands/permitCliReads/colorize.ts
3623
+ import chalk43 from "chalk";
3624
+ function colorize(plainOutput) {
3625
+ return plainOutput.split("\n").map((line) => {
3626
+ if (line.startsWith(" R ")) return chalk43.green(line);
3627
+ if (line.startsWith(" W ")) return chalk43.red(line);
3628
+ return line;
3629
+ }).join("\n");
3630
+ }
3631
+
3632
+ // src/lib/isClaudeCode.ts
3633
+ function isClaudeCode() {
3634
+ return process.env.CLAUDECODE !== void 0;
3635
+ }
3636
+
3637
+ // src/commands/permitCliReads/mapAsync.ts
3638
+ async function mapAsync(items, concurrency, fn) {
3639
+ const results = new Array(items.length);
3640
+ let next2 = 0;
3641
+ async function worker() {
3642
+ while (next2 < items.length) {
3643
+ const idx = next2++;
3644
+ results[idx] = await fn(items[idx]);
3645
+ }
3646
+ }
3647
+ const workers = Array.from(
3648
+ { length: Math.min(concurrency, items.length) },
3649
+ () => worker()
3650
+ );
3651
+ await Promise.all(workers);
3652
+ return results;
3653
+ }
3654
+
3655
+ // src/commands/permitCliReads/parseCommands.ts
3656
+ var COMMAND_SECTION_RE = /^((?:core |general |available |additional |other |management |targeted |alias |github actions )?(?:commands|subgroups)):?$/i;
3657
+ function isSkippable(name) {
3658
+ return name.startsWith("-") || name.startsWith("<") || name.startsWith("[");
3659
+ }
3660
+ function parseCommandLine(trimmed) {
3661
+ const azMatch = trimmed.match(/^(\S+)\s+(?:\[.*?]\s+)?:\s*(.+)/);
3662
+ if (azMatch && !isSkippable(azMatch[1])) {
3663
+ return { name: azMatch[1], description: azMatch[2].trim() };
3664
+ }
3665
+ const colonMatch = trimmed.match(/^(\S+?):\s{2,}(.+)/);
3666
+ if (colonMatch && !isSkippable(colonMatch[1])) {
3667
+ return { name: colonMatch[1], description: colonMatch[2].trim() };
3668
+ }
3669
+ const spaceMatch = trimmed.match(/^(\S+)(?:,\s*\S+)?\s{2,}(.+)/);
3670
+ if (spaceMatch && !isSkippable(spaceMatch[1])) {
3671
+ return { name: spaceMatch[1], description: spaceMatch[2].trim() };
3672
+ }
3673
+ if (/^\S+$/.test(trimmed) && !isSkippable(trimmed)) {
3674
+ return { name: trimmed, description: "" };
3675
+ }
3676
+ return void 0;
3677
+ }
3678
+ function parseCommands(helpText) {
3679
+ const commands = [];
3680
+ let inCommandSection = false;
3681
+ for (const line of helpText.split("\n")) {
3682
+ const trimmed = line.trim();
3683
+ if (COMMAND_SECTION_RE.test(trimmed)) {
3684
+ inCommandSection = true;
3685
+ continue;
3686
+ }
3687
+ if (inCommandSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
3688
+ inCommandSection = false;
3689
+ continue;
3690
+ }
3691
+ if (!inCommandSection || !trimmed) continue;
3692
+ if (trimmed.startsWith("-") || trimmed.startsWith("=")) continue;
3693
+ const parsed = parseCommandLine(trimmed);
3694
+ if (parsed) commands.push(parsed);
3695
+ }
3696
+ return commands;
3697
+ }
3698
+ var COMMAND_SECTION_MULTILINE_RE = new RegExp(
3699
+ COMMAND_SECTION_RE.source,
3700
+ "im"
3701
+ );
3702
+ function hasSubcommands(helpText) {
3703
+ return COMMAND_SECTION_MULTILINE_RE.test(helpText);
3704
+ }
3705
+
3706
+ // src/commands/permitCliReads/runHelp.ts
3707
+ import { exec as exec2 } from "child_process";
3708
+ function runHelp(args) {
3709
+ return new Promise((resolve5) => {
3710
+ exec2(
3711
+ `${args.join(" ")} --help`,
3712
+ { encoding: "utf-8", timeout: 3e4 },
3713
+ (_err, stdout, stderr) => {
3714
+ resolve5(stdout || stderr || "");
3715
+ }
3716
+ );
3717
+ });
3718
+ }
3719
+
3720
+ // src/commands/permitCliReads/discoverAll.ts
3721
+ var SAFETY_DEPTH = 10;
3722
+ var CONCURRENCY = 8;
3723
+ var interactive = !isClaudeCode();
3724
+ function showProgress(p, label2) {
3725
+ if (!interactive) return;
3726
+ const pct = Math.round(p.done / p.total * 100);
3727
+ process.stderr.write(`\r\x1B[K[${pct}%] Scanning ${label2}...`);
3728
+ }
3729
+ async function resolveCommand(cli, path31, description, depth, p) {
3730
+ showProgress(p, path31.join(" "));
3731
+ const subHelp = await runHelp([cli, ...path31]);
3732
+ if (!subHelp || !hasSubcommands(subHelp)) {
3733
+ return [{ path: path31, description }];
3734
+ }
3735
+ const children = await discoverAt(cli, path31, depth + 1, p);
3736
+ return children.length > 0 ? children : [{ path: path31, description }];
3737
+ }
3738
+ async function discoverAt(cli, parentPath, depth, p) {
3739
+ if (depth > SAFETY_DEPTH) return [];
3740
+ const helpText = await runHelp([cli, ...parentPath]);
3741
+ if (!helpText) return [];
3742
+ const cmds = parseCommands(helpText);
3743
+ const results = await mapAsync(
3744
+ cmds,
3745
+ CONCURRENCY,
3746
+ (cmd) => resolveCommand(cli, [...parentPath, cmd.name], cmd.description, depth, p)
3747
+ );
3748
+ return results.flat();
3749
+ }
3750
+ async function discoverAll(cli) {
3751
+ const topLevel = parseCommands(await runHelp([cli]));
3752
+ const p = { done: 0, total: topLevel.length };
3753
+ const results = await mapAsync(topLevel, CONCURRENCY, async (cmd) => {
3754
+ showProgress(p, cmd.name);
3755
+ const resolved = await resolveCommand(
3756
+ cli,
3757
+ [cmd.name],
3758
+ cmd.description,
3759
+ 1,
3760
+ p
3761
+ );
3762
+ p.done++;
3763
+ showProgress(p, cmd.name);
3764
+ return resolved;
3765
+ });
3766
+ if (interactive) process.stderr.write("\r\x1B[K");
3767
+ return results.flat();
3768
+ }
3769
+
3770
+ // src/commands/permitCliReads/classifyVerb.ts
3771
+ var READ_VERBS = /* @__PURE__ */ new Set([
3772
+ "list",
3773
+ "show",
3774
+ "view",
3775
+ "export",
3776
+ "get",
3777
+ "diff",
3778
+ "status",
3779
+ "search",
3780
+ "checks",
3781
+ "describe",
3782
+ "inspect",
3783
+ "logs",
3784
+ "cat",
3785
+ "top",
3786
+ "explain",
3787
+ "exists",
3788
+ "browse",
3789
+ "watch"
3790
+ ]);
3791
+ var WRITE_VERBS = /* @__PURE__ */ new Set([
3792
+ "create",
3793
+ "delete",
3794
+ "import",
3795
+ "set",
3796
+ "update",
3797
+ "merge",
3798
+ "close",
3799
+ "reopen",
3800
+ "edit",
3801
+ "apply",
3802
+ "patch",
3803
+ "drain",
3804
+ "cordon",
3805
+ "taint",
3806
+ "push",
3807
+ "deploy",
3808
+ "add",
3809
+ "remove",
3810
+ "assign",
3811
+ "unassign",
3812
+ "lock",
3813
+ "unlock",
3814
+ "start",
3815
+ "stop",
3816
+ "restart",
3817
+ "enable",
3818
+ "disable",
3819
+ "revoke",
3820
+ "rotate"
3821
+ ]);
3822
+ function classifyVerb(verb) {
3823
+ if (READ_VERBS.has(verb)) return "r";
3824
+ if (WRITE_VERBS.has(verb)) return "w";
3825
+ return "?";
3826
+ }
3827
+
3828
+ // src/commands/permitCliReads/formatHuman.ts
3829
+ function prefix(kind) {
3830
+ if (kind === "r") return " R ";
3831
+ if (kind === "w") return " W ";
3832
+ return " ? ";
3695
3833
  }
3696
- function printVersionInfo(config, lastInfo, firstHash) {
3697
- const versions = computeVersions(lastInfo);
3698
- displayVersion(
3699
- !!config.commit?.conventional,
3700
- firstHash,
3701
- versions.patch,
3702
- versions.minor
3834
+ function formatHuman(cli, commands) {
3835
+ const sorted = [...commands].sort(
3836
+ (a, b) => a.path.join(" ").localeCompare(b.path.join(" "))
3703
3837
  );
3838
+ const lines = [`Discovered ${commands.length} commands for "${cli}":
3839
+ `];
3840
+ for (const cmd of sorted) {
3841
+ const verb = cmd.path[cmd.path.length - 1];
3842
+ const full = `${cli} ${cmd.path.join(" ")}`;
3843
+ const text = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
3844
+ lines.push(`${prefix(classifyVerb(verb))}${text}`);
3845
+ }
3846
+ return lines.join("\n");
3704
3847
  }
3705
- function resolveIgnoreList(options2, config) {
3706
- return options2.ignore ?? config.devlog?.ignore ?? [];
3707
- }
3708
- function resolveSkipDays(config) {
3709
- return new Set(config.devlog?.skip?.days ?? []);
3710
- }
3711
- function getLastDate(lastInfo) {
3712
- return lastInfo?.date ?? null;
3713
- }
3714
- function getCommitsForDate(commitsByDate, date) {
3715
- return commitsByDate.get(date) ?? [];
3716
- }
3717
- function noCommitsMessage(hasLastInfo) {
3718
- return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
3719
- }
3720
- function logName(repoName) {
3721
- console.log(`${chalk41.bold("name:")} ${repoName}`);
3722
- }
3723
- function displayNextEntry(ctx, targetDate, commits) {
3724
- logName(ctx.repoName);
3725
- printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
3726
- console.log(chalk41.bold.blue(targetDate));
3727
- printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
3848
+
3849
+ // src/commands/permitCliReads/parseCached.ts
3850
+ function parseCached(cli, cached) {
3851
+ const prefix2 = `${cli} `;
3852
+ const commands = [];
3853
+ for (const line of cached.split("\n")) {
3854
+ const trimmed = line.replace(/^ [RW?] {2}/, "").trim();
3855
+ if (!trimmed.startsWith(prefix2)) continue;
3856
+ const rest = trimmed.slice(prefix2.length);
3857
+ const dashIdx = rest.indexOf(" \u2014 ");
3858
+ const pathStr = dashIdx >= 0 ? rest.slice(0, dashIdx) : rest;
3859
+ const description = dashIdx >= 0 ? rest.slice(dashIdx + 3) : "";
3860
+ commands.push({ path: pathStr.split(" "), description });
3861
+ }
3862
+ return commands;
3728
3863
  }
3729
- function logNoCommits(lastInfo) {
3730
- console.log(chalk41.dim(noCommitsMessage(!!lastInfo)));
3864
+
3865
+ // src/commands/permitCliReads/updateSettings.ts
3866
+ function updateSettings(cli, commands) {
3867
+ const existing = loadCliReads();
3868
+ const readEntries = commands.filter((cmd) => classifyVerb(cmd.path[cmd.path.length - 1]) === "r").map((cmd) => `${cli} ${cmd.path.join(" ")}`);
3869
+ const merged = [.../* @__PURE__ */ new Set([...existing, ...readEntries])].sort();
3870
+ if (merged.length === existing.length && merged.every((e, i) => e === existing[i]))
3871
+ return;
3872
+ saveCliReads(merged);
3731
3873
  }
3732
3874
 
3733
- // src/commands/devlog/next/index.ts
3734
- function resolveContextData(config, options2) {
3735
- const repoName = getRepoName();
3736
- const lastInfo = getLastVersionInfo(repoName, config);
3737
- return { repoName, lastInfo, ignore: resolveIgnoreList(options2, config) };
3875
+ // src/commands/permitCliReads/index.ts
3876
+ function logPath(cli) {
3877
+ const safeName = cli.replace(/\s+/g, "-");
3878
+ return join12(homedir4(), ".assist", `cli-discover-${safeName}.log`);
3738
3879
  }
3739
- function buildContext(options2) {
3740
- const config = loadConfig();
3741
- const data = resolveContextData(config, options2);
3742
- return { config, ...data, verbose: options2.verbose ?? false };
3880
+ function readCache(cli) {
3881
+ const path31 = logPath(cli);
3882
+ if (!existsSync18(path31)) return void 0;
3883
+ return readFileSync16(path31, "utf-8");
3743
3884
  }
3744
- function fetchNextCommits(ctx) {
3745
- const commitsByDate = fetchCommitsByDate(
3746
- ctx.ignore,
3747
- getLastDate(ctx.lastInfo)
3748
- );
3749
- const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
3750
- return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
3885
+ function writeCache(cli, output) {
3886
+ const dir = join12(homedir4(), ".assist");
3887
+ mkdirSync4(dir, { recursive: true });
3888
+ writeFileSync14(logPath(cli), output);
3751
3889
  }
3752
- function showResult(ctx, found) {
3753
- if (!found) {
3754
- logNoCommits(ctx.lastInfo);
3755
- return;
3890
+ async function permitCliReads(cli, options2 = { noCache: false }) {
3891
+ if (!cli) {
3892
+ console.error("Usage: assist permit-cli-reads <cli>");
3893
+ process.exit(1);
3756
3894
  }
3757
- displayNextEntry(ctx, found.targetDate, found.commits);
3758
- }
3759
- function next(options2) {
3760
- const ctx = buildContext(options2);
3761
- showResult(ctx, fetchNextCommits(ctx));
3762
- }
3763
-
3764
- // src/commands/devlog/skip.ts
3765
- import chalk42 from "chalk";
3766
- function skip(date) {
3767
- if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
3768
- console.log(chalk42.red("Invalid date format. Use YYYY-MM-DD"));
3895
+ const installDir = getInstallDir();
3896
+ if (!isGitRepo(installDir)) {
3897
+ console.error(
3898
+ "permit-cli-reads must be run from the assist git repo, not a global install."
3899
+ );
3769
3900
  process.exit(1);
3770
3901
  }
3771
- const config = loadProjectConfig();
3772
- const devlog = config.devlog ?? {};
3773
- const skip2 = devlog.skip ?? {};
3774
- const skipDays = skip2.days ?? [];
3775
- if (skipDays.includes(date)) {
3776
- console.log(chalk42.yellow(`${date} is already in skip list`));
3777
- return;
3902
+ if (!options2.noCache) {
3903
+ const cached = readCache(cli);
3904
+ if (cached) {
3905
+ console.log(colorize(cached));
3906
+ updateSettings(cli, parseCached(cli, cached));
3907
+ return;
3908
+ }
3778
3909
  }
3779
- skipDays.push(date);
3780
- skipDays.sort();
3781
- skip2.days = skipDays;
3782
- devlog.skip = skip2;
3783
- config.devlog = devlog;
3784
- saveConfig(config);
3785
- console.log(chalk42.green(`Added ${date} to skip list`));
3786
- }
3787
-
3788
- // src/commands/devlog/version.ts
3789
- import chalk43 from "chalk";
3790
- function version() {
3791
- const config = loadConfig();
3792
- const name = getRepoName();
3793
- const lastInfo = getLastVersionInfo(name, config);
3794
- const lastVersion = lastInfo?.version ?? null;
3795
- const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
3796
- console.log(`${chalk43.bold("name:")} ${name}`);
3797
- console.log(`${chalk43.bold("last:")} ${lastVersion ?? chalk43.dim("none")}`);
3798
- console.log(`${chalk43.bold("next:")} ${nextVersion ?? chalk43.dim("none")}`);
3910
+ assertCliExists(cli);
3911
+ const commands = await discoverAll(cli);
3912
+ const output = formatHuman(cli, commands);
3913
+ console.log(colorize(output));
3914
+ writeCache(cli, output);
3915
+ updateSettings(cli, commands);
3799
3916
  }
3800
3917
 
3801
- // src/commands/registerDevlog.ts
3802
- function registerDevlog(program2) {
3803
- const devlogCommand = program2.command("devlog").description("Development log utilities");
3804
- devlogCommand.command("list").description("Group git commits by date").option(
3805
- "--days <number>",
3806
- "Number of days to show (default: 30)",
3807
- Number.parseInt
3808
- ).option("--since <date>", "Only show commits since this date (YYYY-MM-DD)").option("-r, --reverse", "Show earliest commits first").option("-v, --verbose", "Show file names for each commit").action(list3);
3809
- devlogCommand.command("version").description("Show current repo name and version info").action(version);
3810
- devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
3811
- devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
3918
+ // src/commands/registerPermitCliReads.ts
3919
+ function registerPermitCliReads(program2) {
3920
+ program2.command("permit-cli-reads").description("Discover a CLI's commands and auto-permit read-only ones").argument(
3921
+ "<cli...>",
3922
+ "CLI binary and optional subcommand (e.g. gh, az, acli jira)"
3923
+ ).option("--no-cache", "Force fresh discovery, ignoring cached results").action((cli, options2) => {
3924
+ permitCliReads(cli.join(" "), { noCache: !options2.cache });
3925
+ });
3812
3926
  }
3813
3927
 
3814
3928
  // src/commands/prs/comment.ts
3815
3929
  import { spawnSync as spawnSync2 } from "child_process";
3816
3930
  import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
3817
3931
  import { tmpdir as tmpdir2 } from "os";
3818
- import { join as join14 } from "path";
3932
+ import { join as join13 } from "path";
3819
3933
 
3820
3934
  // src/commands/prs/shared.ts
3821
- import { execSync as execSync18 } from "child_process";
3935
+ import { execSync as execSync19 } from "child_process";
3822
3936
  function isGhNotInstalled(error) {
3823
3937
  if (error instanceof Error) {
3824
3938
  const msg = error.message.toLowerCase();
@@ -3834,14 +3948,14 @@ function isNotFound(error) {
3834
3948
  }
3835
3949
  function getRepoInfo() {
3836
3950
  const repoInfo = JSON.parse(
3837
- execSync18("gh repo view --json owner,name", { encoding: "utf-8" })
3951
+ execSync19("gh repo view --json owner,name", { encoding: "utf-8" })
3838
3952
  );
3839
3953
  return { org: repoInfo.owner.login, repo: repoInfo.name };
3840
3954
  }
3841
3955
  function getCurrentPrNumber() {
3842
3956
  try {
3843
3957
  const prInfo = JSON.parse(
3844
- execSync18("gh pr view --json number", { encoding: "utf-8" })
3958
+ execSync19("gh pr view --json number", { encoding: "utf-8" })
3845
3959
  );
3846
3960
  return prInfo.number;
3847
3961
  } catch (error) {
@@ -3855,7 +3969,7 @@ function getCurrentPrNumber() {
3855
3969
  function getCurrentPrNodeId() {
3856
3970
  try {
3857
3971
  const prInfo = JSON.parse(
3858
- execSync18("gh pr view --json id", { encoding: "utf-8" })
3972
+ execSync19("gh pr view --json id", { encoding: "utf-8" })
3859
3973
  );
3860
3974
  return prInfo.id;
3861
3975
  } catch (error) {
@@ -3887,7 +4001,7 @@ function comment(path31, line, body) {
3887
4001
  validateLine(line);
3888
4002
  try {
3889
4003
  const prId = getCurrentPrNodeId();
3890
- const queryFile = join14(tmpdir2(), `gh-query-${Date.now()}.graphql`);
4004
+ const queryFile = join13(tmpdir2(), `gh-query-${Date.now()}.graphql`);
3891
4005
  writeFileSync15(queryFile, MUTATION);
3892
4006
  try {
3893
4007
  const result = spawnSync2(
@@ -3926,32 +4040,32 @@ function comment(path31, line, body) {
3926
4040
  }
3927
4041
 
3928
4042
  // src/commands/prs/fixed.ts
3929
- import { execSync as execSync20 } from "child_process";
4043
+ import { execSync as execSync21 } from "child_process";
3930
4044
 
3931
4045
  // src/commands/prs/resolveCommentWithReply.ts
3932
- import { execSync as execSync19 } from "child_process";
4046
+ import { execSync as execSync20 } from "child_process";
3933
4047
  import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "fs";
3934
4048
  import { tmpdir as tmpdir3 } from "os";
3935
- import { join as join16 } from "path";
4049
+ import { join as join15 } from "path";
3936
4050
 
3937
4051
  // src/commands/prs/loadCommentsCache.ts
3938
- import { existsSync as existsSync18, readFileSync as readFileSync16, unlinkSync as unlinkSync4 } from "fs";
3939
- import { join as join15 } from "path";
4052
+ import { existsSync as existsSync19, readFileSync as readFileSync17, unlinkSync as unlinkSync4 } from "fs";
4053
+ import { join as join14 } from "path";
3940
4054
  import { parse } from "yaml";
3941
4055
  function getCachePath(prNumber) {
3942
- return join15(process.cwd(), ".assist", `pr-${prNumber}-comments.yaml`);
4056
+ return join14(process.cwd(), ".assist", `pr-${prNumber}-comments.yaml`);
3943
4057
  }
3944
4058
  function loadCommentsCache(prNumber) {
3945
4059
  const cachePath = getCachePath(prNumber);
3946
- if (!existsSync18(cachePath)) {
4060
+ if (!existsSync19(cachePath)) {
3947
4061
  return null;
3948
4062
  }
3949
- const content = readFileSync16(cachePath, "utf-8");
4063
+ const content = readFileSync17(cachePath, "utf-8");
3950
4064
  return parse(content);
3951
4065
  }
3952
4066
  function deleteCommentsCache(prNumber) {
3953
4067
  const cachePath = getCachePath(prNumber);
3954
- if (existsSync18(cachePath)) {
4068
+ if (existsSync19(cachePath)) {
3955
4069
  unlinkSync4(cachePath);
3956
4070
  console.log("No more unresolved line comments. Cache dropped.");
3957
4071
  }
@@ -3959,17 +4073,17 @@ function deleteCommentsCache(prNumber) {
3959
4073
 
3960
4074
  // src/commands/prs/resolveCommentWithReply.ts
3961
4075
  function replyToComment(org, repo, prNumber, commentId, message) {
3962
- execSync19(
4076
+ execSync20(
3963
4077
  `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
3964
4078
  { stdio: "inherit" }
3965
4079
  );
3966
4080
  }
3967
4081
  function resolveThread(threadId) {
3968
4082
  const mutation = `mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }`;
3969
- const queryFile = join16(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
4083
+ const queryFile = join15(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
3970
4084
  writeFileSync16(queryFile, mutation);
3971
4085
  try {
3972
- execSync19(
4086
+ execSync20(
3973
4087
  `gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
3974
4088
  { stdio: "inherit" }
3975
4089
  );
@@ -4021,7 +4135,7 @@ function resolveCommentWithReply(commentId, message) {
4021
4135
  // src/commands/prs/fixed.ts
4022
4136
  function verifySha(sha) {
4023
4137
  try {
4024
- return execSync20(`git rev-parse --verify ${sha}`, {
4138
+ return execSync21(`git rev-parse --verify ${sha}`, {
4025
4139
  encoding: "utf-8"
4026
4140
  }).trim();
4027
4141
  } catch {
@@ -4047,21 +4161,21 @@ function fixed(commentId, sha) {
4047
4161
  }
4048
4162
 
4049
4163
  // src/commands/prs/listComments/index.ts
4050
- import { existsSync as existsSync19, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
4051
- import { join as join18 } from "path";
4164
+ import { existsSync as existsSync20, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
4165
+ import { join as join17 } from "path";
4052
4166
  import { stringify } from "yaml";
4053
4167
 
4054
4168
  // src/commands/prs/fetchThreadIds.ts
4055
- import { execSync as execSync21 } from "child_process";
4169
+ import { execSync as execSync22 } from "child_process";
4056
4170
  import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "fs";
4057
4171
  import { tmpdir as tmpdir4 } from "os";
4058
- import { join as join17 } from "path";
4172
+ import { join as join16 } from "path";
4059
4173
  var THREAD_QUERY = `query($owner: String!, $repo: String!, $prNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $prNumber) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 100) { nodes { databaseId } } } } } } }`;
4060
4174
  function fetchThreadIds(org, repo, prNumber) {
4061
- const queryFile = join17(tmpdir4(), `gh-query-${Date.now()}.graphql`);
4175
+ const queryFile = join16(tmpdir4(), `gh-query-${Date.now()}.graphql`);
4062
4176
  writeFileSync17(queryFile, THREAD_QUERY);
4063
4177
  try {
4064
- const result = execSync21(
4178
+ const result = execSync22(
4065
4179
  `gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
4066
4180
  { encoding: "utf-8" }
4067
4181
  );
@@ -4083,9 +4197,9 @@ function fetchThreadIds(org, repo, prNumber) {
4083
4197
  }
4084
4198
 
4085
4199
  // src/commands/prs/listComments/fetchReviewComments.ts
4086
- import { execSync as execSync22 } from "child_process";
4200
+ import { execSync as execSync23 } from "child_process";
4087
4201
  function fetchJson(endpoint) {
4088
- const result = execSync22(`gh api --paginate ${endpoint}`, {
4202
+ const result = execSync23(`gh api --paginate ${endpoint}`, {
4089
4203
  encoding: "utf-8"
4090
4204
  });
4091
4205
  if (!result.trim()) return [];
@@ -4163,8 +4277,8 @@ function printComments(comments) {
4163
4277
  }
4164
4278
  }
4165
4279
  function writeCommentsCache(prNumber, comments) {
4166
- const assistDir = join18(process.cwd(), ".assist");
4167
- if (!existsSync19(assistDir)) {
4280
+ const assistDir = join17(process.cwd(), ".assist");
4281
+ if (!existsSync20(assistDir)) {
4168
4282
  mkdirSync5(assistDir, { recursive: true });
4169
4283
  }
4170
4284
  const cacheData = {
@@ -4172,7 +4286,7 @@ function writeCommentsCache(prNumber, comments) {
4172
4286
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
4173
4287
  comments
4174
4288
  };
4175
- const cachePath = join18(assistDir, `pr-${prNumber}-comments.yaml`);
4289
+ const cachePath = join17(assistDir, `pr-${prNumber}-comments.yaml`);
4176
4290
  writeFileSync18(cachePath, stringify(cacheData));
4177
4291
  }
4178
4292
  function handleKnownErrors(error) {
@@ -4213,7 +4327,7 @@ async function listComments() {
4213
4327
  }
4214
4328
 
4215
4329
  // src/commands/prs/prs/index.ts
4216
- import { execSync as execSync23 } from "child_process";
4330
+ import { execSync as execSync24 } from "child_process";
4217
4331
 
4218
4332
  // src/commands/prs/prs/displayPaginated/index.ts
4219
4333
  import enquirer5 from "enquirer";
@@ -4319,7 +4433,7 @@ async function displayPaginated(pullRequests) {
4319
4433
  async function prs(options2) {
4320
4434
  const state = options2.open ? "open" : options2.closed ? "closed" : "all";
4321
4435
  try {
4322
- const result = execSync23(
4436
+ const result = execSync24(
4323
4437
  `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
4324
4438
  { encoding: "utf-8" }
4325
4439
  );
@@ -4342,7 +4456,7 @@ async function prs(options2) {
4342
4456
  }
4343
4457
 
4344
4458
  // src/commands/prs/wontfix.ts
4345
- import { execSync as execSync24 } from "child_process";
4459
+ import { execSync as execSync25 } from "child_process";
4346
4460
  function validateReason(reason) {
4347
4461
  const lowerReason = reason.toLowerCase();
4348
4462
  if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
@@ -4359,7 +4473,7 @@ function validateShaReferences(reason) {
4359
4473
  const invalidShas = [];
4360
4474
  for (const sha of shas) {
4361
4475
  try {
4362
- execSync24(`git cat-file -t ${sha}`, { stdio: "pipe" });
4476
+ execSync25(`git cat-file -t ${sha}`, { stdio: "pipe" });
4363
4477
  } catch {
4364
4478
  invalidShas.push(sha);
4365
4479
  }
@@ -4463,7 +4577,7 @@ Refactor check failed:
4463
4577
  }
4464
4578
 
4465
4579
  // src/commands/refactor/check/getViolations/index.ts
4466
- import { execSync as execSync25 } from "child_process";
4580
+ import { execSync as execSync26 } from "child_process";
4467
4581
  import fs15 from "fs";
4468
4582
  import { minimatch as minimatch4 } from "minimatch";
4469
4583
 
@@ -4513,7 +4627,7 @@ function getGitFiles(options2) {
4513
4627
  }
4514
4628
  const files = /* @__PURE__ */ new Set();
4515
4629
  if (options2.staged || options2.modified) {
4516
- const staged = execSync25("git diff --cached --name-only", {
4630
+ const staged = execSync26("git diff --cached --name-only", {
4517
4631
  encoding: "utf-8"
4518
4632
  });
4519
4633
  for (const file of staged.trim().split("\n").filter(Boolean)) {
@@ -4521,7 +4635,7 @@ function getGitFiles(options2) {
4521
4635
  }
4522
4636
  }
4523
4637
  if (options2.unstaged || options2.modified) {
4524
- const unstaged = execSync25("git diff --name-only", { encoding: "utf-8" });
4638
+ const unstaged = execSync26("git diff --name-only", { encoding: "utf-8" });
4525
4639
  for (const file of unstaged.trim().split("\n").filter(Boolean)) {
4526
4640
  files.add(file);
4527
4641
  }
@@ -4551,7 +4665,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
4551
4665
 
4552
4666
  // src/commands/refactor/check/index.ts
4553
4667
  function runScript(script, cwd) {
4554
- return new Promise((resolve3) => {
4668
+ return new Promise((resolve5) => {
4555
4669
  const child = spawn3("npm", ["run", script], {
4556
4670
  stdio: "pipe",
4557
4671
  shell: true,
@@ -4565,7 +4679,7 @@ function runScript(script, cwd) {
4565
4679
  output += data.toString();
4566
4680
  });
4567
4681
  child.on("close", (code) => {
4568
- resolve3({ script, code: code ?? 1, output });
4682
+ resolve5({ script, code: code ?? 1, output });
4569
4683
  });
4570
4684
  });
4571
4685
  }
@@ -5137,8 +5251,8 @@ function registerRefactor(program2) {
5137
5251
  }
5138
5252
 
5139
5253
  // src/commands/transcript/shared.ts
5140
- import { existsSync as existsSync20, readdirSync as readdirSync2, statSync } from "fs";
5141
- import { basename as basename4, join as join19, relative } from "path";
5254
+ import { existsSync as existsSync21, readdirSync as readdirSync2, statSync } from "fs";
5255
+ import { basename as basename4, join as join18, relative } from "path";
5142
5256
  import * as readline2 from "readline";
5143
5257
  var DATE_PREFIX_REGEX = /^\d{4}-\d{2}-\d{2}/;
5144
5258
  function getDatePrefix(daysOffset = 0) {
@@ -5153,10 +5267,10 @@ function isValidDatePrefix(filename) {
5153
5267
  return DATE_PREFIX_REGEX.test(filename);
5154
5268
  }
5155
5269
  function collectFiles(dir, extension) {
5156
- if (!existsSync20(dir)) return [];
5270
+ if (!existsSync21(dir)) return [];
5157
5271
  const results = [];
5158
5272
  for (const entry of readdirSync2(dir)) {
5159
- const fullPath = join19(dir, entry);
5273
+ const fullPath = join18(dir, entry);
5160
5274
  if (statSync(fullPath).isDirectory()) {
5161
5275
  results.push(...collectFiles(fullPath, extension));
5162
5276
  } else if (entry.endsWith(extension)) {
@@ -5188,9 +5302,9 @@ function createReadlineInterface() {
5188
5302
  });
5189
5303
  }
5190
5304
  function askQuestion(rl, question) {
5191
- return new Promise((resolve3) => {
5305
+ return new Promise((resolve5) => {
5192
5306
  rl.question(question, (answer) => {
5193
- resolve3(answer.trim());
5307
+ resolve5(answer.trim());
5194
5308
  });
5195
5309
  });
5196
5310
  }
@@ -5250,14 +5364,14 @@ async function configure() {
5250
5364
  }
5251
5365
 
5252
5366
  // src/commands/transcript/format/index.ts
5253
- import { existsSync as existsSync22 } from "fs";
5367
+ import { existsSync as existsSync23 } from "fs";
5254
5368
 
5255
5369
  // src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
5256
- import { dirname as dirname13, join as join21 } from "path";
5370
+ import { dirname as dirname15, join as join20 } from "path";
5257
5371
 
5258
5372
  // src/commands/transcript/format/fixInvalidDatePrefixes/promptForDateFix.ts
5259
5373
  import { renameSync } from "fs";
5260
- import { join as join20 } from "path";
5374
+ import { join as join19 } from "path";
5261
5375
  async function resolveDate(rl, choice) {
5262
5376
  if (choice === "1") return getDatePrefix(0);
5263
5377
  if (choice === "2") return getDatePrefix(-1);
@@ -5272,7 +5386,7 @@ async function resolveDate(rl, choice) {
5272
5386
  }
5273
5387
  function renameWithPrefix(vttDir, vttFile, prefix2) {
5274
5388
  const newFilename = `${prefix2}.${vttFile}`;
5275
- renameSync(join20(vttDir, vttFile), join20(vttDir, newFilename));
5389
+ renameSync(join19(vttDir, vttFile), join19(vttDir, newFilename));
5276
5390
  console.log(`Renamed to: ${newFilename}`);
5277
5391
  return newFilename;
5278
5392
  }
@@ -5303,15 +5417,15 @@ async function fixInvalidDatePrefixes(vttFiles) {
5303
5417
  for (let i = 0; i < vttFiles.length; i++) {
5304
5418
  const vttFile = vttFiles[i];
5305
5419
  if (!isValidDatePrefix(vttFile.filename)) {
5306
- const vttFileDir = dirname13(vttFile.absolutePath);
5420
+ const vttFileDir = dirname15(vttFile.absolutePath);
5307
5421
  const newFilename = await promptForDateFix(vttFile.filename, vttFileDir);
5308
5422
  if (newFilename) {
5309
- const newRelativePath = join21(
5310
- dirname13(vttFile.relativePath),
5423
+ const newRelativePath = join20(
5424
+ dirname15(vttFile.relativePath),
5311
5425
  newFilename
5312
5426
  );
5313
5427
  vttFiles[i] = {
5314
- absolutePath: join21(vttFileDir, newFilename),
5428
+ absolutePath: join20(vttFileDir, newFilename),
5315
5429
  relativePath: newRelativePath,
5316
5430
  filename: newFilename
5317
5431
  };
@@ -5324,8 +5438,8 @@ async function fixInvalidDatePrefixes(vttFiles) {
5324
5438
  }
5325
5439
 
5326
5440
  // src/commands/transcript/format/processVttFile/index.ts
5327
- import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync17, writeFileSync as writeFileSync19 } from "fs";
5328
- import { basename as basename5, dirname as dirname14, join as join22 } from "path";
5441
+ import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync19 } from "fs";
5442
+ import { basename as basename5, dirname as dirname16, join as join21 } from "path";
5329
5443
 
5330
5444
  // src/commands/transcript/cleanText.ts
5331
5445
  function cleanText(text) {
@@ -5535,21 +5649,21 @@ function toMdFilename(vttFilename) {
5535
5649
  return `${basename5(vttFilename, ".vtt").replace(/\s*Transcription\s*/g, " ").trim()}.md`;
5536
5650
  }
5537
5651
  function resolveOutputDir(relativeDir, transcriptsDir) {
5538
- return relativeDir === "." ? transcriptsDir : join22(transcriptsDir, relativeDir);
5652
+ return relativeDir === "." ? transcriptsDir : join21(transcriptsDir, relativeDir);
5539
5653
  }
5540
5654
  function buildOutputPaths(vttFile, transcriptsDir) {
5541
5655
  const mdFile = toMdFilename(vttFile.filename);
5542
- const relativeDir = dirname14(vttFile.relativePath);
5656
+ const relativeDir = dirname16(vttFile.relativePath);
5543
5657
  const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
5544
- const outputPath = join22(outputDir, mdFile);
5658
+ const outputPath = join21(outputDir, mdFile);
5545
5659
  return { outputDir, outputPath, mdFile, relativeDir };
5546
5660
  }
5547
5661
  function logSkipped(relativeDir, mdFile) {
5548
- console.log(`Skipping (already exists): ${join22(relativeDir, mdFile)}`);
5662
+ console.log(`Skipping (already exists): ${join21(relativeDir, mdFile)}`);
5549
5663
  return "skipped";
5550
5664
  }
5551
5665
  function ensureDirectory(dir, label2) {
5552
- if (!existsSync21(dir)) {
5666
+ if (!existsSync22(dir)) {
5553
5667
  mkdirSync6(dir, { recursive: true });
5554
5668
  console.log(`Created ${label2}: ${dir}`);
5555
5669
  }
@@ -5572,7 +5686,7 @@ function logReduction(cueCount, messageCount) {
5572
5686
  }
5573
5687
  function readAndParseCues(inputPath) {
5574
5688
  console.log(`Reading: ${inputPath}`);
5575
- return processCues(readFileSync17(inputPath, "utf-8"));
5689
+ return processCues(readFileSync18(inputPath, "utf-8"));
5576
5690
  }
5577
5691
  function writeFormatted(outputPath, content) {
5578
5692
  writeFileSync19(outputPath, content, "utf-8");
@@ -5585,7 +5699,7 @@ function convertVttToMarkdown(inputPath, outputPath) {
5585
5699
  logReduction(cues.length, chatMessages.length);
5586
5700
  }
5587
5701
  function tryProcessVtt(vttFile, paths) {
5588
- if (existsSync21(paths.outputPath))
5702
+ if (existsSync22(paths.outputPath))
5589
5703
  return logSkipped(paths.relativeDir, paths.mdFile);
5590
5704
  convertVttToMarkdown(vttFile.absolutePath, paths.outputPath);
5591
5705
  return "processed";
@@ -5611,7 +5725,7 @@ function processAllFiles(vttFiles, transcriptsDir) {
5611
5725
  logSummary(counts);
5612
5726
  }
5613
5727
  function requireVttDir(vttDir) {
5614
- if (!existsSync22(vttDir)) {
5728
+ if (!existsSync23(vttDir)) {
5615
5729
  console.error(`VTT directory not found: ${vttDir}`);
5616
5730
  process.exit(1);
5617
5731
  }
@@ -5643,18 +5757,18 @@ async function format() {
5643
5757
  }
5644
5758
 
5645
5759
  // src/commands/transcript/summarise/index.ts
5646
- import { existsSync as existsSync24 } from "fs";
5647
- import { basename as basename6, dirname as dirname16, join as join24, relative as relative2 } from "path";
5760
+ import { existsSync as existsSync25 } from "fs";
5761
+ import { basename as basename6, dirname as dirname18, join as join23, relative as relative2 } from "path";
5648
5762
 
5649
5763
  // src/commands/transcript/summarise/processStagedFile/index.ts
5650
5764
  import {
5651
- existsSync as existsSync23,
5765
+ existsSync as existsSync24,
5652
5766
  mkdirSync as mkdirSync7,
5653
- readFileSync as readFileSync18,
5767
+ readFileSync as readFileSync19,
5654
5768
  renameSync as renameSync2,
5655
5769
  rmSync
5656
5770
  } from "fs";
5657
- import { dirname as dirname15, join as join23 } from "path";
5771
+ import { dirname as dirname17, join as join22 } from "path";
5658
5772
 
5659
5773
  // src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
5660
5774
  import chalk51 from "chalk";
@@ -5683,9 +5797,9 @@ function validateStagedContent(filename, content) {
5683
5797
  }
5684
5798
 
5685
5799
  // src/commands/transcript/summarise/processStagedFile/index.ts
5686
- var STAGING_DIR = join23(process.cwd(), ".assist", "transcript");
5800
+ var STAGING_DIR = join22(process.cwd(), ".assist", "transcript");
5687
5801
  function processStagedFile() {
5688
- if (!existsSync23(STAGING_DIR)) {
5802
+ if (!existsSync24(STAGING_DIR)) {
5689
5803
  return false;
5690
5804
  }
5691
5805
  const stagedFiles = findMdFilesRecursive(STAGING_DIR);
@@ -5694,7 +5808,7 @@ function processStagedFile() {
5694
5808
  }
5695
5809
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
5696
5810
  const stagedFile = stagedFiles[0];
5697
- const content = readFileSync18(stagedFile.absolutePath, "utf-8");
5811
+ const content = readFileSync19(stagedFile.absolutePath, "utf-8");
5698
5812
  validateStagedContent(stagedFile.filename, content);
5699
5813
  const stagedBaseName = getTranscriptBaseName(stagedFile.filename);
5700
5814
  const transcriptFiles = findMdFilesRecursive(transcriptsDir);
@@ -5707,9 +5821,9 @@ function processStagedFile() {
5707
5821
  );
5708
5822
  process.exit(1);
5709
5823
  }
5710
- const destPath = join23(summaryDir, matchingTranscript.relativePath);
5711
- const destDir = dirname15(destPath);
5712
- if (!existsSync23(destDir)) {
5824
+ const destPath = join22(summaryDir, matchingTranscript.relativePath);
5825
+ const destDir = dirname17(destPath);
5826
+ if (!existsSync24(destDir)) {
5713
5827
  mkdirSync7(destDir, { recursive: true });
5714
5828
  }
5715
5829
  renameSync2(stagedFile.absolutePath, destPath);
@@ -5722,8 +5836,8 @@ function processStagedFile() {
5722
5836
 
5723
5837
  // src/commands/transcript/summarise/index.ts
5724
5838
  function buildRelativeKey(relativePath, baseName) {
5725
- const relDir = dirname16(relativePath);
5726
- return relDir === "." ? baseName : join24(relDir, baseName);
5839
+ const relDir = dirname18(relativePath);
5840
+ return relDir === "." ? baseName : join23(relDir, baseName);
5727
5841
  }
5728
5842
  function buildSummaryIndex(summaryDir) {
5729
5843
  const summaryFiles = findMdFilesRecursive(summaryDir);
@@ -5736,7 +5850,7 @@ function buildSummaryIndex(summaryDir) {
5736
5850
  function summarise() {
5737
5851
  processStagedFile();
5738
5852
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
5739
- if (!existsSync24(transcriptsDir)) {
5853
+ if (!existsSync25(transcriptsDir)) {
5740
5854
  console.log("No transcripts directory found.");
5741
5855
  return;
5742
5856
  }
@@ -5757,8 +5871,8 @@ function summarise() {
5757
5871
  }
5758
5872
  const next2 = missing[0];
5759
5873
  const outputFilename = `${getTranscriptBaseName(next2.filename)}.md`;
5760
- const outputPath = join24(STAGING_DIR, outputFilename);
5761
- const summaryFileDir = join24(summaryDir, dirname16(next2.relativePath));
5874
+ const outputPath = join23(STAGING_DIR, outputFilename);
5875
+ const summaryFileDir = join23(summaryDir, dirname18(next2.relativePath));
5762
5876
  const relativeTranscriptPath = encodeURI(
5763
5877
  relative2(summaryFileDir, next2.absolutePath).replace(/\\/g, "/")
5764
5878
  );
@@ -5804,50 +5918,50 @@ function registerVerify(program2) {
5804
5918
 
5805
5919
  // src/commands/voice/devices.ts
5806
5920
  import { spawnSync as spawnSync3 } from "child_process";
5807
- import { join as join26 } from "path";
5921
+ import { join as join25 } from "path";
5808
5922
 
5809
5923
  // src/commands/voice/shared.ts
5810
5924
  import { homedir as homedir5 } from "os";
5811
- import { dirname as dirname17, join as join25 } from "path";
5812
- import { fileURLToPath as fileURLToPath4 } from "url";
5813
- var __dirname5 = dirname17(fileURLToPath4(import.meta.url));
5814
- var VOICE_DIR = join25(homedir5(), ".assist", "voice");
5925
+ import { dirname as dirname19, join as join24 } from "path";
5926
+ import { fileURLToPath as fileURLToPath6 } from "url";
5927
+ var __dirname7 = dirname19(fileURLToPath6(import.meta.url));
5928
+ var VOICE_DIR = join24(homedir5(), ".assist", "voice");
5815
5929
  var voicePaths = {
5816
5930
  dir: VOICE_DIR,
5817
- pid: join25(VOICE_DIR, "voice.pid"),
5818
- log: join25(VOICE_DIR, "voice.log"),
5819
- venv: join25(VOICE_DIR, ".venv"),
5820
- lock: join25(VOICE_DIR, "voice.lock")
5931
+ pid: join24(VOICE_DIR, "voice.pid"),
5932
+ log: join24(VOICE_DIR, "voice.log"),
5933
+ venv: join24(VOICE_DIR, ".venv"),
5934
+ lock: join24(VOICE_DIR, "voice.lock")
5821
5935
  };
5822
5936
  function getPythonDir() {
5823
- return join25(__dirname5, "commands", "voice", "python");
5937
+ return join24(__dirname7, "commands", "voice", "python");
5824
5938
  }
5825
5939
  function getVenvPython() {
5826
- return process.platform === "win32" ? join25(voicePaths.venv, "Scripts", "python.exe") : join25(voicePaths.venv, "bin", "python");
5940
+ return process.platform === "win32" ? join24(voicePaths.venv, "Scripts", "python.exe") : join24(voicePaths.venv, "bin", "python");
5827
5941
  }
5828
5942
  function getLockDir() {
5829
5943
  const config = loadConfig();
5830
5944
  return config.voice?.lockDir ?? VOICE_DIR;
5831
5945
  }
5832
5946
  function getLockFile() {
5833
- return join25(getLockDir(), "voice.lock");
5947
+ return join24(getLockDir(), "voice.lock");
5834
5948
  }
5835
5949
 
5836
5950
  // src/commands/voice/devices.ts
5837
5951
  function devices() {
5838
- const script = join26(getPythonDir(), "list_devices.py");
5952
+ const script = join25(getPythonDir(), "list_devices.py");
5839
5953
  spawnSync3(getVenvPython(), [script], { stdio: "inherit" });
5840
5954
  }
5841
5955
 
5842
5956
  // src/commands/voice/logs.ts
5843
- import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
5957
+ import { existsSync as existsSync26, readFileSync as readFileSync20 } from "fs";
5844
5958
  function logs(options2) {
5845
- if (!existsSync25(voicePaths.log)) {
5959
+ if (!existsSync26(voicePaths.log)) {
5846
5960
  console.log("No voice log file found");
5847
5961
  return;
5848
5962
  }
5849
5963
  const count = Number.parseInt(options2.lines ?? "150", 10);
5850
- const content = readFileSync19(voicePaths.log, "utf-8").trim();
5964
+ const content = readFileSync20(voicePaths.log, "utf-8").trim();
5851
5965
  if (!content) {
5852
5966
  console.log("Voice log is empty");
5853
5967
  return;
@@ -5870,12 +5984,12 @@ function logs(options2) {
5870
5984
  // src/commands/voice/setup.ts
5871
5985
  import { spawnSync as spawnSync4 } from "child_process";
5872
5986
  import { mkdirSync as mkdirSync9 } from "fs";
5873
- import { join as join28 } from "path";
5987
+ import { join as join27 } from "path";
5874
5988
 
5875
5989
  // src/commands/voice/checkLockFile.ts
5876
- import { execSync as execSync26 } from "child_process";
5877
- import { existsSync as existsSync26, mkdirSync as mkdirSync8, readFileSync as readFileSync20, writeFileSync as writeFileSync20 } from "fs";
5878
- import { join as join27 } from "path";
5990
+ import { execSync as execSync27 } from "child_process";
5991
+ import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync20 } from "fs";
5992
+ import { join as join26 } from "path";
5879
5993
  function isProcessAlive(pid) {
5880
5994
  try {
5881
5995
  process.kill(pid, 0);
@@ -5886,9 +6000,9 @@ function isProcessAlive(pid) {
5886
6000
  }
5887
6001
  function checkLockFile() {
5888
6002
  const lockFile = getLockFile();
5889
- if (!existsSync26(lockFile)) return;
6003
+ if (!existsSync27(lockFile)) return;
5890
6004
  try {
5891
- const lock = JSON.parse(readFileSync20(lockFile, "utf-8"));
6005
+ const lock = JSON.parse(readFileSync21(lockFile, "utf-8"));
5892
6006
  if (lock.pid && isProcessAlive(lock.pid)) {
5893
6007
  console.error(
5894
6008
  `Voice daemon already running (PID ${lock.pid}, env: ${lock.env}). Stop it first with: assist voice stop`
@@ -5899,10 +6013,10 @@ function checkLockFile() {
5899
6013
  }
5900
6014
  }
5901
6015
  function bootstrapVenv() {
5902
- if (existsSync26(getVenvPython())) return;
6016
+ if (existsSync27(getVenvPython())) return;
5903
6017
  console.log("Setting up Python environment...");
5904
6018
  const pythonDir = getPythonDir();
5905
- execSync26(
6019
+ execSync27(
5906
6020
  `uv sync --project "${pythonDir}" --extra runtime --no-install-project`,
5907
6021
  {
5908
6022
  stdio: "inherit",
@@ -5912,7 +6026,7 @@ function bootstrapVenv() {
5912
6026
  }
5913
6027
  function writeLockFile(pid) {
5914
6028
  const lockFile = getLockFile();
5915
- mkdirSync8(join27(lockFile, ".."), { recursive: true });
6029
+ mkdirSync8(join26(lockFile, ".."), { recursive: true });
5916
6030
  writeFileSync20(
5917
6031
  lockFile,
5918
6032
  JSON.stringify({
@@ -5928,7 +6042,7 @@ function setup() {
5928
6042
  mkdirSync9(voicePaths.dir, { recursive: true });
5929
6043
  bootstrapVenv();
5930
6044
  console.log("\nDownloading models...\n");
5931
- const script = join28(getPythonDir(), "setup_models.py");
6045
+ const script = join27(getPythonDir(), "setup_models.py");
5932
6046
  const result = spawnSync4(getVenvPython(), [script], {
5933
6047
  stdio: "inherit",
5934
6048
  env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
@@ -5942,7 +6056,7 @@ function setup() {
5942
6056
  // src/commands/voice/start.ts
5943
6057
  import { spawn as spawn4 } from "child_process";
5944
6058
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync21 } from "fs";
5945
- import { join as join29 } from "path";
6059
+ import { join as join28 } from "path";
5946
6060
 
5947
6061
  // src/commands/voice/buildDaemonEnv.ts
5948
6062
  function buildDaemonEnv(options2) {
@@ -5980,7 +6094,7 @@ function start2(options2) {
5980
6094
  bootstrapVenv();
5981
6095
  const debug = options2.debug || options2.foreground || process.platform === "win32";
5982
6096
  const env = buildDaemonEnv({ debug });
5983
- const script = join29(getPythonDir(), "voice_daemon.py");
6097
+ const script = join28(getPythonDir(), "voice_daemon.py");
5984
6098
  const python = getVenvPython();
5985
6099
  if (options2.foreground) {
5986
6100
  spawnForeground(python, script, env);
@@ -5990,7 +6104,7 @@ function start2(options2) {
5990
6104
  }
5991
6105
 
5992
6106
  // src/commands/voice/status.ts
5993
- import { existsSync as existsSync27, readFileSync as readFileSync21 } from "fs";
6107
+ import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
5994
6108
  function isProcessAlive2(pid) {
5995
6109
  try {
5996
6110
  process.kill(pid, 0);
@@ -6000,16 +6114,16 @@ function isProcessAlive2(pid) {
6000
6114
  }
6001
6115
  }
6002
6116
  function readRecentLogs(count) {
6003
- if (!existsSync27(voicePaths.log)) return [];
6004
- const lines = readFileSync21(voicePaths.log, "utf-8").trim().split("\n");
6117
+ if (!existsSync28(voicePaths.log)) return [];
6118
+ const lines = readFileSync22(voicePaths.log, "utf-8").trim().split("\n");
6005
6119
  return lines.slice(-count);
6006
6120
  }
6007
6121
  function status() {
6008
- if (!existsSync27(voicePaths.pid)) {
6122
+ if (!existsSync28(voicePaths.pid)) {
6009
6123
  console.log("Voice daemon: not running (no PID file)");
6010
6124
  return;
6011
6125
  }
6012
- const pid = Number.parseInt(readFileSync21(voicePaths.pid, "utf-8").trim(), 10);
6126
+ const pid = Number.parseInt(readFileSync22(voicePaths.pid, "utf-8").trim(), 10);
6013
6127
  const alive = isProcessAlive2(pid);
6014
6128
  console.log(`Voice daemon: ${alive ? "running" : "dead"} (PID ${pid})`);
6015
6129
  const recent = readRecentLogs(5);
@@ -6028,13 +6142,13 @@ function status() {
6028
6142
  }
6029
6143
 
6030
6144
  // src/commands/voice/stop.ts
6031
- import { existsSync as existsSync28, readFileSync as readFileSync22, unlinkSync as unlinkSync7 } from "fs";
6145
+ import { existsSync as existsSync29, readFileSync as readFileSync23, unlinkSync as unlinkSync7 } from "fs";
6032
6146
  function stop() {
6033
- if (!existsSync28(voicePaths.pid)) {
6147
+ if (!existsSync29(voicePaths.pid)) {
6034
6148
  console.log("Voice daemon is not running (no PID file)");
6035
6149
  return;
6036
6150
  }
6037
- const pid = Number.parseInt(readFileSync22(voicePaths.pid, "utf-8").trim(), 10);
6151
+ const pid = Number.parseInt(readFileSync23(voicePaths.pid, "utf-8").trim(), 10);
6038
6152
  try {
6039
6153
  process.kill(pid, "SIGTERM");
6040
6154
  console.log(`Sent SIGTERM to voice daemon (PID ${pid})`);
@@ -6047,7 +6161,7 @@ function stop() {
6047
6161
  }
6048
6162
  try {
6049
6163
  const lockFile = getLockFile();
6050
- if (existsSync28(lockFile)) unlinkSync7(lockFile);
6164
+ if (existsSync29(lockFile)) unlinkSync7(lockFile);
6051
6165
  } catch {
6052
6166
  }
6053
6167
  console.log("Voice daemon stopped");
@@ -6069,11 +6183,11 @@ import { randomBytes } from "crypto";
6069
6183
  import chalk52 from "chalk";
6070
6184
 
6071
6185
  // src/lib/openBrowser.ts
6072
- import { execSync as execSync27 } from "child_process";
6186
+ import { execSync as execSync28 } from "child_process";
6073
6187
  function tryExec(commands) {
6074
6188
  for (const cmd of commands) {
6075
6189
  try {
6076
- execSync27(cmd);
6190
+ execSync28(cmd);
6077
6191
  return true;
6078
6192
  } catch {
6079
6193
  }
@@ -6131,7 +6245,7 @@ function extractCode(url, expectedState) {
6131
6245
  return code;
6132
6246
  }
6133
6247
  function waitForCallback(port, expectedState) {
6134
- return new Promise((resolve3, reject) => {
6248
+ return new Promise((resolve5, reject) => {
6135
6249
  const timeout = setTimeout(() => {
6136
6250
  server.close();
6137
6251
  reject(new Error("Authorization timed out after 120 seconds"));
@@ -6148,7 +6262,7 @@ function waitForCallback(port, expectedState) {
6148
6262
  const code = extractCode(url, expectedState);
6149
6263
  respondHtml(res, 200, "Authorization successful!");
6150
6264
  server.close();
6151
- resolve3(code);
6265
+ resolve5(code);
6152
6266
  } catch (err) {
6153
6267
  respondHtml(res, 400, err.message);
6154
6268
  server.close();
@@ -6278,7 +6392,7 @@ import { spawn as spawn5 } from "child_process";
6278
6392
 
6279
6393
  // src/commands/run/add.ts
6280
6394
  import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync22 } from "fs";
6281
- import { join as join30 } from "path";
6395
+ import { join as join29 } from "path";
6282
6396
  function findAddIndex() {
6283
6397
  const addIndex = process.argv.indexOf("add");
6284
6398
  if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
@@ -6332,7 +6446,7 @@ function saveNewRunConfig(name, command, args) {
6332
6446
  saveConfig(config);
6333
6447
  }
6334
6448
  function createCommandFile(name) {
6335
- const dir = join30(".claude", "commands");
6449
+ const dir = join29(".claude", "commands");
6336
6450
  mkdirSync11(dir, { recursive: true });
6337
6451
  const content = `---
6338
6452
  description: Run ${name}
@@ -6340,7 +6454,7 @@ description: Run ${name}
6340
6454
 
6341
6455
  Run \`assist run ${name} $ARGUMENTS 2>&1\`.
6342
6456
  `;
6343
- const filePath = join30(dir, `${name}.md`);
6457
+ const filePath = join29(dir, `${name}.md`);
6344
6458
  writeFileSync22(filePath, content);
6345
6459
  console.log(`Created command file: ${filePath}`);
6346
6460
  }
@@ -6439,7 +6553,7 @@ async function statusLine() {
6439
6553
  import * as fs23 from "fs";
6440
6554
  import * as os from "os";
6441
6555
  import * as path29 from "path";
6442
- import { fileURLToPath as fileURLToPath5 } from "url";
6556
+ import { fileURLToPath as fileURLToPath7 } from "url";
6443
6557
 
6444
6558
  // src/commands/sync/syncClaudeMd.ts
6445
6559
  import * as fs21 from "fs";
@@ -6479,11 +6593,15 @@ async function syncSettings(claudeDir, targetBase, options2) {
6479
6593
  const source = path28.join(claudeDir, "settings.json");
6480
6594
  const target = path28.join(targetBase, "settings.json");
6481
6595
  const sourceContent = fs22.readFileSync(source, "utf-8");
6482
- const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
6596
+ const mergedContent = JSON.stringify(JSON.parse(sourceContent), null, " ");
6483
6597
  if (fs22.existsSync(target)) {
6484
6598
  const targetContent = fs22.readFileSync(target, "utf-8");
6485
- const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
6486
- if (normalizedSource !== normalizedTarget) {
6599
+ const normalizedTarget = JSON.stringify(
6600
+ JSON.parse(targetContent),
6601
+ null,
6602
+ " "
6603
+ );
6604
+ if (mergedContent !== normalizedTarget) {
6487
6605
  if (!options2?.yes) {
6488
6606
  console.log(
6489
6607
  chalk55.yellow(
@@ -6491,7 +6609,7 @@ async function syncSettings(claudeDir, targetBase, options2) {
6491
6609
  )
6492
6610
  );
6493
6611
  console.log();
6494
- printDiff(targetContent, sourceContent);
6612
+ printDiff(targetContent, mergedContent);
6495
6613
  const confirm = await promptConfirm(
6496
6614
  chalk55.red("Overwrite existing settings.json?"),
6497
6615
  false
@@ -6503,15 +6621,15 @@ async function syncSettings(claudeDir, targetBase, options2) {
6503
6621
  }
6504
6622
  }
6505
6623
  }
6506
- fs22.copyFileSync(source, target);
6624
+ fs22.writeFileSync(target, mergedContent);
6507
6625
  console.log("Copied settings.json to ~/.claude/settings.json");
6508
6626
  }
6509
6627
 
6510
6628
  // src/commands/sync.ts
6511
- var __filename2 = fileURLToPath5(import.meta.url);
6512
- var __dirname6 = path29.dirname(__filename2);
6629
+ var __filename4 = fileURLToPath7(import.meta.url);
6630
+ var __dirname8 = path29.dirname(__filename4);
6513
6631
  async function sync(options2) {
6514
- const claudeDir = path29.join(__dirname6, "..", "claude");
6632
+ const claudeDir = path29.join(__dirname8, "..", "claude");
6515
6633
  const targetBase = path29.join(os.homedir(), ".claude");
6516
6634
  syncCommands(claudeDir, targetBase);
6517
6635
  await syncSettings(claudeDir, targetBase, { yes: options2?.yes });
@@ -6530,28 +6648,11 @@ function syncCommands(claudeDir, targetBase) {
6530
6648
  }
6531
6649
 
6532
6650
  // src/commands/update.ts
6533
- import { execSync as execSync28 } from "child_process";
6651
+ import { execSync as execSync29 } from "child_process";
6534
6652
  import * as path30 from "path";
6535
- import { fileURLToPath as fileURLToPath6 } from "url";
6536
- var __filename3 = fileURLToPath6(import.meta.url);
6537
- var __dirname7 = path30.dirname(__filename3);
6538
- function getInstallDir() {
6539
- return path30.resolve(__dirname7, "..");
6540
- }
6541
- function isGitRepo(dir) {
6542
- try {
6543
- const result = execSync28("git rev-parse --show-toplevel", {
6544
- cwd: dir,
6545
- stdio: "pipe"
6546
- }).toString().trim();
6547
- return path30.resolve(result) === path30.resolve(dir);
6548
- } catch {
6549
- return false;
6550
- }
6551
- }
6552
6653
  function isGlobalNpmInstall(dir) {
6553
6654
  try {
6554
- const globalPrefix = execSync28("npm prefix -g", { stdio: "pipe" }).toString().trim();
6655
+ const globalPrefix = execSync29("npm prefix -g", { stdio: "pipe" }).toString().trim();
6555
6656
  return path30.resolve(dir).toLowerCase().startsWith(path30.resolve(globalPrefix).toLowerCase());
6556
6657
  } catch {
6557
6658
  return false;
@@ -6562,16 +6663,16 @@ async function update() {
6562
6663
  console.log(`Assist is installed at: ${installDir}`);
6563
6664
  if (isGitRepo(installDir)) {
6564
6665
  console.log("Detected git repo installation, pulling latest...");
6565
- execSync28("git pull", { cwd: installDir, stdio: "inherit" });
6666
+ execSync29("git pull", { cwd: installDir, stdio: "inherit" });
6566
6667
  console.log("Building...");
6567
- execSync28("npm run build", { cwd: installDir, stdio: "inherit" });
6668
+ execSync29("npm run build", { cwd: installDir, stdio: "inherit" });
6568
6669
  console.log("Syncing commands...");
6569
- execSync28("assist sync", { stdio: "inherit" });
6670
+ execSync29("assist sync", { stdio: "inherit" });
6570
6671
  } else if (isGlobalNpmInstall(installDir)) {
6571
6672
  console.log("Detected global npm installation, updating...");
6572
- execSync28("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6673
+ execSync29("npm i -g @staff0rd/assist@latest", { stdio: "inherit" });
6573
6674
  console.log("Syncing commands...");
6574
- execSync28("assist sync", { stdio: "inherit" });
6675
+ execSync29("assist sync", { stdio: "inherit" });
6575
6676
  } else {
6576
6677
  console.error(
6577
6678
  "Could not determine installation method. Expected a git repo or global npm install."
@@ -6605,7 +6706,7 @@ program.command("notify").description(
6605
6706
  "Show notification from Claude Code hook (reads JSON from stdin)"
6606
6707
  ).action(notify);
6607
6708
  program.command("update").description("Update assist to the latest version and sync commands").action(update);
6608
- registerCliDiscover(program);
6709
+ registerPermitCliReads(program);
6609
6710
  registerCliHook(program);
6610
6711
  registerPrs(program);
6611
6712
  registerRoam(program);