@staff0rd/assist 0.45.0 → 0.47.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
@@ -7,7 +7,7 @@ import { Command } from "commander";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@staff0rd/assist",
10
- version: "0.45.0",
10
+ version: "0.47.0",
11
11
  type: "module",
12
12
  main: "dist/index.js",
13
13
  bin: {
@@ -28,7 +28,7 @@ var package_default = {
28
28
  "verify:types": "tsc --noEmit",
29
29
  "verify:knip": "knip --no-progress --treat-config-hints-as-errors",
30
30
  "verify:duplicate-code": "jscpd --format 'typescript,tsx' --exitCode 1 --ignore '**/*.test.*' -r consoleFull src",
31
- "verify:maintainability": "assist complexity maintainability ./src --threshold 65",
31
+ "verify:maintainability": "assist complexity maintainability ./src --threshold 70",
32
32
  "verify:custom-lint": "assist lint"
33
33
  },
34
34
  keywords: [],
@@ -214,65 +214,96 @@ function commit(message) {
214
214
  // src/commands/config/index.ts
215
215
  import chalk2 from "chalk";
216
216
  import { stringify as stringifyYaml2 } from "yaml";
217
- function getNestedValue(obj, path28) {
218
- const keys = path28.split(".");
217
+
218
+ // src/commands/config/getNestedValue.ts
219
+ function isTraversable(value) {
220
+ return value !== null && value !== void 0 && typeof value === "object";
221
+ }
222
+ function stepInto(current, key) {
223
+ return isTraversable(current) ? current[key] : void 0;
224
+ }
225
+ function getNestedValue(obj, path29) {
219
226
  let current = obj;
220
- for (const key of keys) {
221
- if (current === null || current === void 0 || typeof current !== "object") {
222
- return void 0;
223
- }
224
- current = current[key];
225
- }
227
+ for (const key of path29.split(".")) current = stepInto(current, key);
226
228
  return current;
227
229
  }
228
- function setNestedValue(obj, path28, value) {
229
- const keys = path28.split(".");
230
- const result = { ...obj };
231
- let current = result;
230
+ function isPlainObject(val) {
231
+ return val !== null && typeof val === "object" && !Array.isArray(val);
232
+ }
233
+ function ensureNestedObject(current, key) {
234
+ current[key] = isPlainObject(current[key]) ? { ...current[key] } : {};
235
+ return current[key];
236
+ }
237
+ function buildNestedPath(root, keys) {
238
+ let current = root;
232
239
  for (let i = 0; i < keys.length - 1; i++) {
233
- const key = keys[i];
234
- current[key] = current[key] !== null && current[key] !== void 0 && typeof current[key] === "object" && !Array.isArray(current[key]) ? { ...current[key] } : {};
235
- current = current[key];
240
+ current = ensureNestedObject(current, keys[i]);
236
241
  }
237
- current[keys[keys.length - 1]] = value;
242
+ return current;
243
+ }
244
+ function setNestedValue(obj, path29, value) {
245
+ const keys = path29.split(".");
246
+ const result = { ...obj };
247
+ buildNestedPath(result, keys)[keys[keys.length - 1]] = value;
238
248
  return result;
239
249
  }
250
+
251
+ // src/commands/config/index.ts
240
252
  function coerceValue(value) {
241
253
  if (value === "true") return true;
242
254
  if (value === "false") return false;
243
255
  return value;
244
256
  }
245
- function configSet(key, value) {
246
- const config = loadConfig();
247
- const coerced = coerceValue(value);
257
+ function formatIssuePath(issue, key) {
258
+ return issue.path.length > 0 ? issue.path.join(".") : key;
259
+ }
260
+ function printValidationErrors(issues, key) {
261
+ for (const issue of issues) {
262
+ console.error(
263
+ chalk2.red(`${formatIssuePath(issue, key)}: ${issue.message}`)
264
+ );
265
+ }
266
+ }
267
+ function exitValidationFailed(issues, key) {
268
+ printValidationErrors(issues, key);
269
+ process.exit(1);
270
+ }
271
+ function validateConfig(updated, key) {
272
+ const result = assistConfigSchema.safeParse(updated);
273
+ if (!result.success) return exitValidationFailed(result.error.issues, key);
274
+ return result.data;
275
+ }
276
+ function applyConfigSet(key, coerced) {
248
277
  const updated = setNestedValue(
249
- config,
278
+ loadConfig(),
250
279
  key,
251
280
  coerced
252
281
  );
253
- const result = assistConfigSchema.safeParse(updated);
254
- if (!result.success) {
255
- for (const issue of result.error.issues) {
256
- const path28 = issue.path.length > 0 ? issue.path.join(".") : key;
257
- console.error(chalk2.red(`${path28}: ${issue.message}`));
258
- }
259
- process.exit(1);
260
- }
261
- saveConfig(result.data);
282
+ saveConfig(validateConfig(updated, key));
283
+ }
284
+ function configSet(key, value) {
285
+ const coerced = coerceValue(value);
286
+ applyConfigSet(key, coerced);
262
287
  console.log(chalk2.green(`Set ${key} = ${JSON.stringify(coerced)}`));
263
288
  }
264
- function configGet(key) {
265
- const config = loadConfig();
289
+ function formatOutput(value) {
290
+ return typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value);
291
+ }
292
+ function exitKeyNotSet(key) {
293
+ console.error(chalk2.red(`Key "${key}" is not set`));
294
+ process.exit(1);
295
+ }
296
+ function requireNestedValue(config, key) {
266
297
  const value = getNestedValue(config, key);
267
- if (value === void 0) {
268
- console.error(chalk2.red(`Key "${key}" is not set`));
269
- process.exit(1);
270
- }
271
- if (typeof value === "object" && value !== null) {
272
- console.log(JSON.stringify(value, null, 2));
273
- } else {
274
- console.log(String(value));
275
- }
298
+ if (value === void 0) return exitKeyNotSet(key);
299
+ return value;
300
+ }
301
+ function configGet(key) {
302
+ console.log(
303
+ formatOutput(
304
+ requireNestedValue(loadConfig(), key)
305
+ )
306
+ );
276
307
  }
277
308
  function configList() {
278
309
  const config = loadConfig();
@@ -750,55 +781,95 @@ function detectExistingSetup(pkg) {
750
781
  }
751
782
 
752
783
  // src/commands/verify/init/getAvailableOptions.ts
753
- function getAvailableOptions(setup) {
754
- const options = [];
755
- if (needsSetup(setup.knip)) {
756
- options.push({
757
- name: `knip${getStatusLabel(setup.knip)}`,
758
- value: "knip",
759
- description: "Dead code and unused dependency detection"
760
- });
761
- }
762
- if (needsSetup(setup.biome)) {
763
- options.push({
764
- name: `lint${getStatusLabel(setup.biome)}`,
765
- value: "lint",
766
- description: "Code linting and formatting with Biome"
767
- });
768
- }
769
- if (needsSetup(setup.jscpd)) {
770
- options.push({
771
- name: `duplicate-code${getStatusLabel(setup.jscpd)}`,
772
- value: "duplicate-code",
773
- description: "Duplicate code detection with jscpd"
774
- });
784
+ function getBuildDescription(setup) {
785
+ if (setup.hasVite && setup.hasTypescript)
786
+ return "TypeScript + Vite build verification";
787
+ if (setup.hasVite) return "Vite build verification";
788
+ return "TypeScript type checking";
789
+ }
790
+ function shouldInclude(setup, def) {
791
+ return needsSetup(setup[def.toolKey]) && (def.extraCondition ?? true);
792
+ }
793
+ function toVerifyOption(setup, def) {
794
+ return {
795
+ name: `${def.label}${getStatusLabel(setup[def.toolKey])}`,
796
+ value: def.value,
797
+ description: def.description
798
+ };
799
+ }
800
+ var STATIC_OPTIONS = [
801
+ {
802
+ toolKey: "knip",
803
+ value: "knip",
804
+ label: "knip",
805
+ description: "Dead code and unused dependency detection"
806
+ },
807
+ {
808
+ toolKey: "biome",
809
+ value: "lint",
810
+ label: "lint",
811
+ description: "Code linting and formatting with Biome"
812
+ },
813
+ {
814
+ toolKey: "jscpd",
815
+ value: "duplicate-code",
816
+ label: "duplicate-code",
817
+ description: "Duplicate code detection with jscpd"
818
+ },
819
+ {
820
+ toolKey: "hardcodedColors",
821
+ value: "hardcoded-colors",
822
+ label: "hardcoded-colors",
823
+ description: "Detect hardcoded hex colors (use open-color instead)"
775
824
  }
776
- if (needsSetup(setup.test) && setup.test.hasPackage) {
777
- options.push({
778
- name: `test${getStatusLabel(setup.test)}`,
825
+ ];
826
+ function getConditionalOptions(setup) {
827
+ return [
828
+ {
829
+ toolKey: "test",
779
830
  value: "test",
780
- description: "Run tests with vitest"
781
- });
782
- }
783
- if (needsSetup(setup.build) && (setup.hasTypescript || setup.hasVite)) {
784
- const description = setup.hasVite ? setup.hasTypescript ? "TypeScript + Vite build verification" : "Vite build verification" : "TypeScript type checking";
785
- options.push({
786
- name: `build${getStatusLabel(setup.build)}`,
831
+ label: "test",
832
+ description: "Run tests with vitest",
833
+ extraCondition: setup.test.hasPackage
834
+ },
835
+ {
836
+ toolKey: "build",
787
837
  value: "build",
788
- description
789
- });
790
- }
791
- if (needsSetup(setup.hardcodedColors)) {
792
- options.push({
793
- name: `hardcoded-colors${getStatusLabel(setup.hardcodedColors)}`,
794
- value: "hardcoded-colors",
795
- description: "Detect hardcoded hex colors (use open-color instead)"
796
- });
797
- }
798
- return options;
838
+ label: "build",
839
+ description: getBuildDescription(setup),
840
+ extraCondition: setup.hasTypescript || setup.hasVite
841
+ }
842
+ ];
843
+ }
844
+ function getAllOptionDefs(setup) {
845
+ return [...STATIC_OPTIONS, ...getConditionalOptions(setup)];
846
+ }
847
+ function getAvailableOptions(setup) {
848
+ return getAllOptionDefs(setup).filter((def) => shouldInclude(setup, def)).map((def) => toVerifyOption(setup, def));
799
849
  }
800
850
 
801
851
  // src/commands/verify/init/index.ts
852
+ function getSetupHandlers(hasVite, hasTypescript, hasOpenColor) {
853
+ return {
854
+ knip: (p) => setupKnip(p),
855
+ lint: (p) => setupLint(p),
856
+ "duplicate-code": (p) => setupDuplicateCode(p),
857
+ test: (p) => setupTest(p),
858
+ build: (p) => setupBuild(p, hasVite, hasTypescript),
859
+ "hardcoded-colors": (p) => setupHardcodedColors(p, hasOpenColor)
860
+ };
861
+ }
862
+ async function runSelectedSetups(selected, packageJsonPath, handlers) {
863
+ for (const choice of selected) {
864
+ await handlers[choice]?.(packageJsonPath);
865
+ }
866
+ console.log(chalk14.green(`
867
+ Added ${selected.length} verify script(s):`));
868
+ for (const choice of selected) {
869
+ console.log(chalk14.green(` - verify:${choice}`));
870
+ }
871
+ console.log(chalk14.dim("\nRun 'assist verify' to run all verify scripts"));
872
+ }
802
873
  async function init2() {
803
874
  const { packageJsonPath, pkg } = requirePackageJson();
804
875
  const setup = detectExistingSetup(pkg);
@@ -816,34 +887,12 @@ async function init2() {
816
887
  console.log(chalk14.yellow("No scripts selected"));
817
888
  return;
818
889
  }
819
- for (const choice of selected) {
820
- switch (choice) {
821
- case "knip":
822
- await setupKnip(packageJsonPath);
823
- break;
824
- case "lint":
825
- await setupLint(packageJsonPath);
826
- break;
827
- case "duplicate-code":
828
- await setupDuplicateCode(packageJsonPath);
829
- break;
830
- case "test":
831
- await setupTest(packageJsonPath);
832
- break;
833
- case "build":
834
- await setupBuild(packageJsonPath, setup.hasVite, setup.hasTypescript);
835
- break;
836
- case "hardcoded-colors":
837
- await setupHardcodedColors(packageJsonPath, setup.hasOpenColor);
838
- break;
839
- }
840
- }
841
- console.log(chalk14.green(`
842
- Added ${selected.length} verify script(s):`));
843
- for (const choice of selected) {
844
- console.log(chalk14.green(` - verify:${choice}`));
845
- }
846
- console.log(chalk14.dim("\nRun 'assist verify' to run all verify scripts"));
890
+ const handlers = getSetupHandlers(
891
+ setup.hasVite,
892
+ setup.hasTypescript,
893
+ setup.hasOpenColor
894
+ );
895
+ await runSelectedSetups(selected, packageJsonPath, handlers);
847
896
  }
848
897
 
849
898
  // src/commands/vscode/init/index.ts
@@ -932,24 +981,33 @@ function detectVscodeSetup(pkg) {
932
981
  }
933
982
 
934
983
  // src/commands/vscode/init/index.ts
935
- async function init3() {
936
- const { pkg } = requirePackageJson();
937
- const setup = detectVscodeSetup(pkg);
938
- const availableOptions = [];
939
- if (!setup.hasLaunchJson && setup.hasVite) {
940
- availableOptions.push({
984
+ var SETUP_HANDLERS = {
985
+ launch: () => createLaunchJson(),
986
+ settings: () => {
987
+ createSettingsJson();
988
+ createExtensionsJson();
989
+ }
990
+ };
991
+ function getAvailableOptions2(setup) {
992
+ const options = [];
993
+ if (!setup.hasLaunchJson && setup.hasVite)
994
+ options.push({
941
995
  name: "launch",
942
996
  value: "launch",
943
997
  description: "Debug configuration for Vite dev server"
944
998
  });
945
- }
946
- if (!setup.hasSettingsJson) {
947
- availableOptions.push({
999
+ if (!setup.hasSettingsJson)
1000
+ options.push({
948
1001
  name: "settings",
949
1002
  value: "settings",
950
1003
  description: "Biome formatter configuration"
951
1004
  });
952
- }
1005
+ return options;
1006
+ }
1007
+ async function init3() {
1008
+ const { pkg } = requirePackageJson();
1009
+ const setup = detectVscodeSetup(pkg);
1010
+ const availableOptions = getAvailableOptions2(setup);
953
1011
  if (availableOptions.length === 0) {
954
1012
  console.log(chalk16.green("VS Code configuration already exists!"));
955
1013
  return;
@@ -965,17 +1023,7 @@ async function init3() {
965
1023
  }
966
1024
  removeVscodeFromGitignore();
967
1025
  ensureVscodeFolder();
968
- for (const choice of selected) {
969
- switch (choice) {
970
- case "launch":
971
- createLaunchJson();
972
- break;
973
- case "settings":
974
- createSettingsJson();
975
- createExtensionsJson();
976
- break;
977
- }
978
- }
1026
+ for (const choice of selected) SETUP_HANDLERS[choice]?.();
979
1027
  console.log(
980
1028
  chalk16.green(`
981
1029
  Added ${selected.length} VS Code configuration(s)`)
@@ -1231,50 +1279,29 @@ Created ${WORKFLOW_PATH}`));
1231
1279
  }
1232
1280
 
1233
1281
  // src/commands/deploy/init/index.ts
1234
- async function init5() {
1235
- console.log(chalk20.bold("Initializing Netlify deployment...\n"));
1236
- const existingSiteId = getExistingSiteId();
1237
- if (existingSiteId) {
1238
- console.log(chalk20.dim(`Using existing site ID: ${existingSiteId}
1239
- `));
1240
- await updateWorkflow(existingSiteId);
1241
- return;
1242
- }
1243
- console.log("Creating Netlify site...\n");
1282
+ async function ensureNetlifyCli() {
1244
1283
  try {
1245
- execSync5("netlify sites:create --disable-linking", {
1246
- stdio: "inherit"
1247
- });
1284
+ execSync5("netlify sites:create --disable-linking", { stdio: "inherit" });
1248
1285
  } catch (error) {
1249
- if (error instanceof Error && error.message.includes("command not found")) {
1250
- console.error(chalk20.red("\nNetlify CLI is not installed.\n"));
1251
- const install = await promptConfirm("Would you like to install it now?");
1252
- if (install) {
1253
- console.log(chalk20.dim("\nInstalling netlify-cli...\n"));
1254
- execSync5("npm install -g netlify-cli", { stdio: "inherit" });
1255
- console.log();
1256
- execSync5("netlify sites:create --disable-linking", {
1257
- stdio: "inherit"
1258
- });
1259
- } else {
1260
- console.log(
1261
- chalk20.yellow(
1262
- "\nInstall it manually with: npm install -g netlify-cli\n"
1263
- )
1264
- );
1265
- process.exit(1);
1266
- }
1267
- } else {
1286
+ if (!(error instanceof Error) || !error.message.includes("command not found"))
1268
1287
  throw error;
1288
+ console.error(chalk20.red("\nNetlify CLI is not installed.\n"));
1289
+ const install = await promptConfirm("Would you like to install it now?");
1290
+ if (!install) {
1291
+ console.log(
1292
+ chalk20.yellow(
1293
+ "\nInstall it manually with: npm install -g netlify-cli\n"
1294
+ )
1295
+ );
1296
+ process.exit(1);
1269
1297
  }
1298
+ console.log(chalk20.dim("\nInstalling netlify-cli...\n"));
1299
+ execSync5("npm install -g netlify-cli", { stdio: "inherit" });
1300
+ console.log();
1301
+ execSync5("netlify sites:create --disable-linking", { stdio: "inherit" });
1270
1302
  }
1271
- const { siteId } = await enquirer3.prompt({
1272
- type: "input",
1273
- name: "siteId",
1274
- message: "Enter the Site ID from above:",
1275
- validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
1276
- });
1277
- await updateWorkflow(siteId);
1303
+ }
1304
+ function printSetupInstructions() {
1278
1305
  console.log(chalk20.bold("\nDeployment initialized successfully!"));
1279
1306
  console.log(
1280
1307
  chalk20.yellow("\nTo complete setup, create a personal access token at:")
@@ -1290,6 +1317,26 @@ async function init5() {
1290
1317
  )
1291
1318
  );
1292
1319
  }
1320
+ async function init5() {
1321
+ console.log(chalk20.bold("Initializing Netlify deployment...\n"));
1322
+ const existingSiteId = getExistingSiteId();
1323
+ if (existingSiteId) {
1324
+ console.log(chalk20.dim(`Using existing site ID: ${existingSiteId}
1325
+ `));
1326
+ await updateWorkflow(existingSiteId);
1327
+ return;
1328
+ }
1329
+ console.log("Creating Netlify site...\n");
1330
+ await ensureNetlifyCli();
1331
+ const { siteId } = await enquirer3.prompt({
1332
+ type: "input",
1333
+ name: "siteId",
1334
+ message: "Enter the Site ID from above:",
1335
+ validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
1336
+ });
1337
+ await updateWorkflow(siteId);
1338
+ printSetupInstructions();
1339
+ }
1293
1340
 
1294
1341
  // src/commands/new/newProject.ts
1295
1342
  async function newProject() {
@@ -1445,6 +1492,9 @@ async function notify() {
1445
1492
  console.log(`Notification sent: ${notification_type} for ${projectName}`);
1446
1493
  }
1447
1494
 
1495
+ // src/commands/complexity/analyze.ts
1496
+ import chalk26 from "chalk";
1497
+
1448
1498
  // src/commands/complexity/cyclomatic.ts
1449
1499
  import chalk22 from "chalk";
1450
1500
 
@@ -1500,46 +1550,43 @@ function findSourceFiles2(pattern2, baseDir = ".") {
1500
1550
 
1501
1551
  // src/commands/complexity/shared/getNodeName.ts
1502
1552
  import ts from "typescript";
1553
+ var FUNCTION_TYPE_CHECKS = [
1554
+ ts.isFunctionDeclaration,
1555
+ ts.isFunctionExpression,
1556
+ ts.isArrowFunction,
1557
+ ts.isMethodDeclaration,
1558
+ ts.isGetAccessor,
1559
+ ts.isSetAccessor,
1560
+ ts.isConstructorDeclaration
1561
+ ];
1562
+ function getIdentifierText(name) {
1563
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
1564
+ return "<computed>";
1565
+ }
1566
+ function getArrowFunctionName(node) {
1567
+ const { parent } = node;
1568
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name))
1569
+ return parent.name.text;
1570
+ if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name))
1571
+ return parent.name.text;
1572
+ return "<arrow>";
1573
+ }
1503
1574
  function getNodeName(node) {
1504
- if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
1575
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node))
1505
1576
  return node.name?.text ?? "<anonymous>";
1506
- }
1507
- if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) {
1508
- if (ts.isIdentifier(node.name)) {
1509
- return node.name.text;
1510
- }
1511
- if (ts.isStringLiteral(node.name)) {
1512
- return node.name.text;
1513
- }
1514
- return "<computed>";
1515
- }
1516
- if (ts.isArrowFunction(node)) {
1517
- const parent = node.parent;
1518
- if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
1519
- return parent.name.text;
1520
- }
1521
- if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
1522
- return parent.name.text;
1523
- }
1524
- return "<arrow>";
1525
- }
1577
+ if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node))
1578
+ return getIdentifierText(node.name);
1579
+ if (ts.isArrowFunction(node)) return getArrowFunctionName(node);
1526
1580
  if (ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
1527
1581
  const prefix = ts.isGetAccessor(node) ? "get " : "set ";
1528
- if (ts.isIdentifier(node.name)) {
1529
- return `${prefix}${node.name.text}`;
1530
- }
1531
- return `${prefix}<computed>`;
1532
- }
1533
- if (ts.isConstructorDeclaration(node)) {
1534
- return "constructor";
1582
+ return `${prefix}${getIdentifierText(node.name)}`;
1535
1583
  }
1584
+ if (ts.isConstructorDeclaration(node)) return "constructor";
1536
1585
  return "<unknown>";
1537
1586
  }
1538
1587
  function hasFunctionBody(node) {
1539
- if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isConstructorDeclaration(node)) {
1540
- return node.body !== void 0;
1541
- }
1542
- return false;
1588
+ if (!FUNCTION_TYPE_CHECKS.some((check2) => check2(node))) return false;
1589
+ return node.body !== void 0;
1543
1590
  }
1544
1591
 
1545
1592
  // src/commands/complexity/shared/calculateCyclomaticComplexity.ts
@@ -1891,9 +1938,41 @@ Total: ${total} lines across ${files.length} files`)
1891
1938
  });
1892
1939
  }
1893
1940
 
1941
+ // src/commands/complexity/analyze.ts
1942
+ async function analyze(pattern2) {
1943
+ const searchPattern = pattern2.includes("*") || pattern2.includes("/") ? pattern2 : `**/${pattern2}`;
1944
+ const files = findSourceFiles2(searchPattern);
1945
+ if (files.length === 0) {
1946
+ console.log(chalk26.yellow("No files found matching pattern"));
1947
+ return;
1948
+ }
1949
+ if (files.length === 1) {
1950
+ const file = files[0];
1951
+ console.log(chalk26.bold.underline("SLOC"));
1952
+ await sloc(file);
1953
+ console.log();
1954
+ console.log(chalk26.bold.underline("Cyclomatic Complexity"));
1955
+ await cyclomatic(file);
1956
+ console.log();
1957
+ console.log(chalk26.bold.underline("Halstead Metrics"));
1958
+ await halstead(file);
1959
+ console.log();
1960
+ console.log(chalk26.bold.underline("Maintainability Index"));
1961
+ await maintainability(file);
1962
+ return;
1963
+ }
1964
+ await maintainability(searchPattern);
1965
+ }
1966
+
1894
1967
  // src/commands/registerComplexity.ts
1895
1968
  function registerComplexity(program2) {
1896
- const complexityCommand = program2.command("complexity").description("Analyze TypeScript code complexity metrics");
1969
+ const complexityCommand = program2.command("complexity").description("Analyze TypeScript code complexity metrics").argument("[pattern]").action((pattern2) => {
1970
+ if (!pattern2) {
1971
+ complexityCommand.help();
1972
+ return;
1973
+ }
1974
+ return analyze(pattern2);
1975
+ });
1897
1976
  complexityCommand.command("cyclomatic [pattern]").description("Calculate cyclomatic complexity per function").option("--threshold <number>", "Max complexity threshold", Number.parseInt).action(cyclomatic);
1898
1977
  complexityCommand.command("halstead [pattern]").description("Calculate Halstead metrics per function").option("--threshold <number>", "Max volume threshold", Number.parseInt).action(halstead);
1899
1978
  complexityCommand.command("maintainability [pattern]").description("Calculate maintainability index per file").option(
@@ -1906,7 +1985,7 @@ function registerComplexity(program2) {
1906
1985
 
1907
1986
  // src/commands/deploy/redirect.ts
1908
1987
  import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "fs";
1909
- import chalk26 from "chalk";
1988
+ import chalk27 from "chalk";
1910
1989
  var TRAILING_SLASH_SCRIPT = ` <script>
1911
1990
  if (!window.location.pathname.endsWith('/')) {
1912
1991
  window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
@@ -1915,22 +1994,22 @@ var TRAILING_SLASH_SCRIPT = ` <script>
1915
1994
  function redirect() {
1916
1995
  const indexPath = "index.html";
1917
1996
  if (!existsSync11(indexPath)) {
1918
- console.log(chalk26.yellow("No index.html found"));
1997
+ console.log(chalk27.yellow("No index.html found"));
1919
1998
  return;
1920
1999
  }
1921
2000
  const content = readFileSync9(indexPath, "utf-8");
1922
2001
  if (content.includes("window.location.pathname.endsWith('/')")) {
1923
- console.log(chalk26.dim("Trailing slash script already present"));
2002
+ console.log(chalk27.dim("Trailing slash script already present"));
1924
2003
  return;
1925
2004
  }
1926
2005
  const headCloseIndex = content.indexOf("</head>");
1927
2006
  if (headCloseIndex === -1) {
1928
- console.log(chalk26.red("Could not find </head> tag in index.html"));
2007
+ console.log(chalk27.red("Could not find </head> tag in index.html"));
1929
2008
  return;
1930
2009
  }
1931
2010
  const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
1932
2011
  writeFileSync8(indexPath, newContent);
1933
- console.log(chalk26.green("Added trailing slash redirect to index.html"));
2012
+ console.log(chalk27.green("Added trailing slash redirect to index.html"));
1934
2013
  }
1935
2014
 
1936
2015
  // src/commands/registerDeploy.ts
@@ -1946,7 +2025,7 @@ import { basename as basename2 } from "path";
1946
2025
 
1947
2026
  // src/commands/devlog/shared.ts
1948
2027
  import { execSync as execSync7 } from "child_process";
1949
- import chalk27 from "chalk";
2028
+ import chalk28 from "chalk";
1950
2029
 
1951
2030
  // src/commands/devlog/loadDevlogEntries.ts
1952
2031
  import { readdirSync, readFileSync as readFileSync10 } from "fs";
@@ -2007,13 +2086,13 @@ function shouldIgnoreCommit(files, ignorePaths) {
2007
2086
  }
2008
2087
  function printCommitsWithFiles(commits, ignore2, verbose) {
2009
2088
  for (const commit2 of commits) {
2010
- console.log(` ${chalk27.yellow(commit2.hash)} ${commit2.message}`);
2089
+ console.log(` ${chalk28.yellow(commit2.hash)} ${commit2.message}`);
2011
2090
  if (verbose) {
2012
2091
  const visibleFiles = commit2.files.filter(
2013
2092
  (file) => !ignore2.some((p) => file.startsWith(p))
2014
2093
  );
2015
2094
  for (const file of visibleFiles) {
2016
- console.log(` ${chalk27.dim(file)}`);
2095
+ console.log(` ${chalk28.dim(file)}`);
2017
2096
  }
2018
2097
  }
2019
2098
  }
@@ -2038,15 +2117,15 @@ function parseGitLogCommits(output, ignore2, afterDate) {
2038
2117
  }
2039
2118
 
2040
2119
  // src/commands/devlog/list/printDateHeader.ts
2041
- import chalk28 from "chalk";
2120
+ import chalk29 from "chalk";
2042
2121
  function printDateHeader(date, isSkipped, entries) {
2043
2122
  if (isSkipped) {
2044
- console.log(`${chalk28.bold.blue(date)} ${chalk28.dim("skipped")}`);
2123
+ console.log(`${chalk29.bold.blue(date)} ${chalk29.dim("skipped")}`);
2045
2124
  } else if (entries && entries.length > 0) {
2046
- const entryInfo = entries.map((e) => `${chalk28.green(e.version)} ${e.title}`).join(" | ");
2047
- console.log(`${chalk28.bold.blue(date)} ${entryInfo}`);
2125
+ const entryInfo = entries.map((e) => `${chalk29.green(e.version)} ${e.title}`).join(" | ");
2126
+ console.log(`${chalk29.bold.blue(date)} ${entryInfo}`);
2048
2127
  } else {
2049
- console.log(`${chalk28.bold.blue(date)} ${chalk28.red("\u26A0 devlog missing")}`);
2128
+ console.log(`${chalk29.bold.blue(date)} ${chalk29.red("\u26A0 devlog missing")}`);
2050
2129
  }
2051
2130
  }
2052
2131
 
@@ -2085,10 +2164,6 @@ function list(options) {
2085
2164
  }
2086
2165
  }
2087
2166
 
2088
- // src/commands/devlog/next/index.ts
2089
- import { execSync as execSync10 } from "child_process";
2090
- import chalk30 from "chalk";
2091
-
2092
2167
  // src/commands/devlog/getLastVersionInfo.ts
2093
2168
  import { execSync as execSync9 } from "child_process";
2094
2169
  import semver from "semver";
@@ -2124,112 +2199,153 @@ function getLastVersionInfoFromGit() {
2124
2199
  return null;
2125
2200
  }
2126
2201
  }
2202
+ function findLastDate(entries) {
2203
+ const dates = Array.from(entries.keys()).sort().reverse();
2204
+ return dates[0] ?? null;
2205
+ }
2127
2206
  function getLastVersionInfo(repoName, config) {
2128
2207
  const entries = loadDevlogEntries(repoName);
2129
- if (entries.size === 0) {
2130
- return null;
2131
- }
2132
- const dates = Array.from(entries.keys()).sort().reverse();
2133
- const lastDate = dates[0];
2134
- if (!lastDate) {
2135
- return null;
2136
- }
2208
+ const lastDate = findLastDate(entries);
2209
+ if (!lastDate) return null;
2137
2210
  if (config?.commit?.conventional) {
2138
2211
  const gitInfo = getLastVersionInfoFromGit();
2139
- if (gitInfo) {
2140
- return { date: lastDate, version: gitInfo.version };
2141
- }
2142
- }
2143
- const lastEntries = entries.get(lastDate);
2144
- const lastVersion = lastEntries?.[0]?.version;
2145
- if (!lastVersion) {
2146
- return null;
2212
+ if (gitInfo) return { date: lastDate, version: gitInfo.version };
2147
2213
  }
2148
- return { date: lastDate, version: lastVersion };
2214
+ const lastVersion = entries.get(lastDate)?.[0]?.version;
2215
+ return lastVersion ? { date: lastDate, version: lastVersion } : null;
2216
+ }
2217
+ function cleanVersion(version2) {
2218
+ return semver.clean(version2) ?? semver.coerce(version2)?.version ?? null;
2149
2219
  }
2150
2220
  function bumpVersion(version2, type) {
2151
- const cleaned = semver.clean(version2) ?? semver.coerce(version2)?.version;
2152
- if (!cleaned) {
2153
- return version2;
2154
- }
2221
+ const cleaned = cleanVersion(version2);
2222
+ if (!cleaned) return version2;
2155
2223
  const bumped = semver.inc(cleaned, type);
2156
- if (!bumped) {
2157
- return version2;
2158
- }
2159
- if (type === "minor") {
2160
- const parsed = semver.parse(bumped);
2161
- return parsed ? `v${parsed.major}.${parsed.minor}` : `v${bumped}`;
2162
- }
2224
+ if (!bumped) return version2;
2225
+ if (type === "minor") return stripToMinor(bumped);
2163
2226
  return `v${bumped}`;
2164
2227
  }
2165
2228
 
2166
- // src/commands/devlog/next/displayVersion.ts
2167
- import chalk29 from "chalk";
2229
+ // src/commands/devlog/next/displayNextEntry/index.ts
2230
+ import { execSync as execSync10 } from "child_process";
2231
+ import chalk31 from "chalk";
2232
+
2233
+ // src/commands/devlog/next/displayNextEntry/displayVersion.ts
2234
+ import chalk30 from "chalk";
2168
2235
  function displayVersion(conventional, firstHash, patchVersion, minorVersion) {
2169
2236
  if (conventional && firstHash) {
2170
2237
  const version2 = getVersionAtCommit(firstHash);
2171
2238
  if (version2) {
2172
- console.log(`${chalk29.bold("version:")} ${stripToMinor(version2)}`);
2239
+ console.log(`${chalk30.bold("version:")} ${stripToMinor(version2)}`);
2173
2240
  } else {
2174
- console.log(`${chalk29.bold("version:")} ${chalk29.red("unknown")}`);
2241
+ console.log(`${chalk30.bold("version:")} ${chalk30.red("unknown")}`);
2175
2242
  }
2176
2243
  } else if (patchVersion && minorVersion) {
2177
2244
  console.log(
2178
- `${chalk29.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
2245
+ `${chalk30.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
2179
2246
  );
2180
2247
  } else {
2181
- console.log(`${chalk29.bold("version:")} v0.1 (initial)`);
2248
+ console.log(`${chalk30.bold("version:")} v0.1 (initial)`);
2182
2249
  }
2183
2250
  }
2184
2251
 
2185
- // src/commands/devlog/next/index.ts
2186
- function next(options) {
2187
- const config = loadConfig();
2188
- const ignore2 = options.ignore ?? config.devlog?.ignore ?? [];
2189
- const skipDays = new Set(config.devlog?.skip?.days ?? []);
2190
- const repoName = getRepoName();
2191
- const lastInfo = getLastVersionInfo(repoName, config);
2192
- const lastDate = lastInfo?.date ?? null;
2193
- const patchVersion = lastInfo ? bumpVersion(lastInfo.version, "patch") : null;
2194
- const minorVersion = lastInfo ? bumpVersion(lastInfo.version, "minor") : null;
2252
+ // src/commands/devlog/next/displayNextEntry/index.ts
2253
+ function computeVersions(lastInfo) {
2254
+ if (!lastInfo) return { patch: null, minor: null };
2255
+ return {
2256
+ patch: bumpVersion(lastInfo.version, "patch"),
2257
+ minor: bumpVersion(lastInfo.version, "minor")
2258
+ };
2259
+ }
2260
+ function findTargetDate(commitsByDate, skipDays) {
2261
+ return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
2262
+ }
2263
+ function fetchCommitsByDate(ignore2, lastDate) {
2195
2264
  const output = execSync10(
2196
2265
  "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
2197
2266
  { encoding: "utf-8" }
2198
2267
  );
2199
- const commitsByDate = parseGitLogCommits(output, ignore2, lastDate);
2200
- const dates = Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort();
2201
- const targetDate = dates[0];
2202
- if (!targetDate) {
2203
- if (lastInfo) {
2204
- console.log(chalk30.dim("No commits after last versioned entry"));
2205
- } else {
2206
- console.log(chalk30.dim("No commits found"));
2207
- }
2208
- return;
2209
- }
2210
- const commits = commitsByDate.get(targetDate) ?? [];
2211
- console.log(`${chalk30.bold("name:")} ${repoName}`);
2268
+ return parseGitLogCommits(output, ignore2, lastDate);
2269
+ }
2270
+ function printVersionInfo(config, lastInfo, firstHash) {
2271
+ const versions = computeVersions(lastInfo);
2212
2272
  displayVersion(
2213
2273
  !!config.commit?.conventional,
2214
- commits[0]?.hash,
2215
- patchVersion,
2216
- minorVersion
2274
+ firstHash,
2275
+ versions.patch,
2276
+ versions.minor
2217
2277
  );
2218
- console.log(`${chalk30.bold.blue(targetDate)}`);
2219
- printCommitsWithFiles(commits, ignore2, options.verbose ?? false);
2278
+ }
2279
+ function resolveIgnoreList(options, config) {
2280
+ return options.ignore ?? config.devlog?.ignore ?? [];
2281
+ }
2282
+ function resolveSkipDays(config) {
2283
+ return new Set(config.devlog?.skip?.days ?? []);
2284
+ }
2285
+ function getLastDate(lastInfo) {
2286
+ return lastInfo?.date ?? null;
2287
+ }
2288
+ function getCommitsForDate(commitsByDate, date) {
2289
+ return commitsByDate.get(date) ?? [];
2290
+ }
2291
+ function noCommitsMessage(hasLastInfo) {
2292
+ return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
2293
+ }
2294
+ function logName(repoName) {
2295
+ console.log(`${chalk31.bold("name:")} ${repoName}`);
2296
+ }
2297
+ function displayNextEntry(ctx, targetDate, commits) {
2298
+ logName(ctx.repoName);
2299
+ printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
2300
+ console.log(chalk31.bold.blue(targetDate));
2301
+ printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
2302
+ }
2303
+ function logNoCommits(lastInfo) {
2304
+ console.log(chalk31.dim(noCommitsMessage(!!lastInfo)));
2305
+ }
2306
+
2307
+ // src/commands/devlog/next/index.ts
2308
+ function resolveContextData(config, options) {
2309
+ const repoName = getRepoName();
2310
+ const lastInfo = getLastVersionInfo(repoName, config);
2311
+ return { repoName, lastInfo, ignore: resolveIgnoreList(options, config) };
2312
+ }
2313
+ function buildContext(options) {
2314
+ const config = loadConfig();
2315
+ const data = resolveContextData(config, options);
2316
+ return { config, ...data, verbose: options.verbose ?? false };
2317
+ }
2318
+ function fetchNextCommits(ctx) {
2319
+ const commitsByDate = fetchCommitsByDate(
2320
+ ctx.ignore,
2321
+ getLastDate(ctx.lastInfo)
2322
+ );
2323
+ const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
2324
+ return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
2325
+ }
2326
+ function showResult(ctx, found) {
2327
+ if (!found) {
2328
+ logNoCommits(ctx.lastInfo);
2329
+ return;
2330
+ }
2331
+ displayNextEntry(ctx, found.targetDate, found.commits);
2332
+ }
2333
+ function next(options) {
2334
+ const ctx = buildContext(options);
2335
+ showResult(ctx, fetchNextCommits(ctx));
2220
2336
  }
2221
2337
 
2222
2338
  // src/commands/devlog/skip.ts
2223
- import chalk31 from "chalk";
2339
+ import chalk32 from "chalk";
2224
2340
  function skip(date) {
2225
2341
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
2226
- console.log(chalk31.red("Invalid date format. Use YYYY-MM-DD"));
2342
+ console.log(chalk32.red("Invalid date format. Use YYYY-MM-DD"));
2227
2343
  process.exit(1);
2228
2344
  }
2229
2345
  const config = loadConfig();
2230
2346
  const skipDays = config.devlog?.skip?.days ?? [];
2231
2347
  if (skipDays.includes(date)) {
2232
- console.log(chalk31.yellow(`${date} is already in skip list`));
2348
+ console.log(chalk32.yellow(`${date} is already in skip list`));
2233
2349
  return;
2234
2350
  }
2235
2351
  skipDays.push(date);
@@ -2242,20 +2358,20 @@ function skip(date) {
2242
2358
  }
2243
2359
  };
2244
2360
  saveConfig(config);
2245
- console.log(chalk31.green(`Added ${date} to skip list`));
2361
+ console.log(chalk32.green(`Added ${date} to skip list`));
2246
2362
  }
2247
2363
 
2248
2364
  // src/commands/devlog/version.ts
2249
- import chalk32 from "chalk";
2365
+ import chalk33 from "chalk";
2250
2366
  function version() {
2251
2367
  const config = loadConfig();
2252
2368
  const name = getRepoName();
2253
2369
  const lastInfo = getLastVersionInfo(name, config);
2254
2370
  const lastVersion = lastInfo?.version ?? null;
2255
2371
  const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
2256
- console.log(`${chalk32.bold("name:")} ${name}`);
2257
- console.log(`${chalk32.bold("last:")} ${lastVersion ?? chalk32.dim("none")}`);
2258
- console.log(`${chalk32.bold("next:")} ${nextVersion ?? chalk32.dim("none")}`);
2372
+ console.log(`${chalk33.bold("name:")} ${name}`);
2373
+ console.log(`${chalk33.bold("last:")} ${lastVersion ?? chalk33.dim("none")}`);
2374
+ console.log(`${chalk33.bold("next:")} ${nextVersion ?? chalk33.dim("none")}`);
2259
2375
  }
2260
2376
 
2261
2377
  // src/commands/registerDevlog.ts
@@ -2356,9 +2472,7 @@ function resolveThread(threadId) {
2356
2472
  unlinkSync3(queryFile);
2357
2473
  }
2358
2474
  }
2359
- function resolveCommentWithReply(commentId, message) {
2360
- const prNumber = getCurrentPrNumber();
2361
- const { org, repo } = getRepoInfo();
2475
+ function requireCache(prNumber) {
2362
2476
  const cache = loadCommentsCache(prNumber);
2363
2477
  if (!cache) {
2364
2478
  console.error(
@@ -2366,27 +2480,37 @@ function resolveCommentWithReply(commentId, message) {
2366
2480
  );
2367
2481
  process.exit(1);
2368
2482
  }
2369
- const comment = cache.comments.find(
2370
- (c) => c.type === "line" && c.id === commentId
2371
- );
2372
- if (!comment || comment.type !== "line") {
2373
- console.error(`Error: Comment #${commentId} not found in cached data.`);
2374
- process.exit(1);
2375
- }
2376
- if (!comment.threadId) {
2377
- console.error(`Error: Comment #${commentId} has no associated thread ID.`);
2483
+ return cache;
2484
+ }
2485
+ function findLineComment(comments, commentId) {
2486
+ return comments.find((c) => c.type === "line" && c.id === commentId);
2487
+ }
2488
+ function requireLineComment(cache, commentId) {
2489
+ const comment = findLineComment(cache.comments, commentId);
2490
+ if (!comment || comment.type !== "line" || !comment.threadId) {
2491
+ console.error(
2492
+ `Error: Comment #${commentId} not found or has no thread ID.`
2493
+ );
2378
2494
  process.exit(1);
2379
2495
  }
2496
+ return comment;
2497
+ }
2498
+ function cleanupCacheIfDone(cache, prNumber, commentId) {
2499
+ const hasRemaining = cache.comments.some(
2500
+ (c) => c.type === "line" && c.id !== commentId
2501
+ );
2502
+ if (!hasRemaining) deleteCommentsCache(prNumber);
2503
+ }
2504
+ function resolveCommentWithReply(commentId, message) {
2505
+ const prNumber = getCurrentPrNumber();
2506
+ const { org, repo } = getRepoInfo();
2507
+ const cache = requireCache(prNumber);
2508
+ const comment = requireLineComment(cache, commentId);
2380
2509
  replyToComment(org, repo, prNumber, commentId, message);
2381
2510
  console.log("Reply posted successfully.");
2382
2511
  resolveThread(comment.threadId);
2383
2512
  console.log("Thread resolved successfully.");
2384
- const remainingLineComments = cache.comments.filter(
2385
- (c) => c.type === "line" && c.id !== commentId
2386
- );
2387
- if (remainingLineComments.length === 0) {
2388
- deleteCommentsCache(prNumber);
2389
- }
2513
+ cleanupCacheIfDone(cache, prNumber, commentId);
2390
2514
  }
2391
2515
 
2392
2516
  // src/commands/prs/fixed.ts
@@ -2449,86 +2573,79 @@ function fetchThreadIds(org, repo, prNumber) {
2449
2573
 
2450
2574
  // src/commands/prs/listComments/fetchReviewComments.ts
2451
2575
  import { execSync as execSync14 } from "child_process";
2452
- function fetchReviewComments(org, repo, prNumber) {
2453
- const result = execSync14(
2454
- `gh api repos/${org}/${repo}/pulls/${prNumber}/reviews`,
2455
- { encoding: "utf-8" }
2456
- );
2576
+ function fetchJson(endpoint) {
2577
+ const result = execSync14(`gh api ${endpoint}`, { encoding: "utf-8" });
2457
2578
  if (!result.trim()) return [];
2458
- const reviews = JSON.parse(result);
2459
- return reviews.filter((r) => r.body).map(
2460
- (r) => ({
2461
- type: "review",
2462
- id: r.id,
2463
- user: r.user.login,
2464
- state: r.state,
2465
- body: r.body
2466
- })
2467
- );
2579
+ return JSON.parse(result);
2580
+ }
2581
+ function mapReview(r) {
2582
+ return {
2583
+ type: "review",
2584
+ id: r.id,
2585
+ user: r.user.login,
2586
+ state: r.state,
2587
+ body: r.body
2588
+ };
2589
+ }
2590
+ function fetchReviewComments(org, repo, prNumber) {
2591
+ const reviews = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/reviews`);
2592
+ return reviews.filter((r) => r.body).map(mapReview);
2593
+ }
2594
+ function mapLineComment(c, threadInfo) {
2595
+ const threadId = threadInfo.threadMap.get(c.id) ?? "";
2596
+ return {
2597
+ type: "line",
2598
+ id: c.id,
2599
+ threadId,
2600
+ user: c.user.login,
2601
+ path: c.path,
2602
+ line: c.line,
2603
+ body: c.body,
2604
+ diff_hunk: c.diff_hunk,
2605
+ html_url: c.html_url,
2606
+ resolved: threadInfo.resolvedThreadIds.has(threadId)
2607
+ };
2468
2608
  }
2469
2609
  function fetchLineComments(org, repo, prNumber, threadInfo) {
2470
- const result = execSync14(
2471
- `gh api repos/${org}/${repo}/pulls/${prNumber}/comments`,
2472
- { encoding: "utf-8" }
2473
- );
2474
- if (!result.trim()) return [];
2475
- const comments = JSON.parse(result);
2610
+ const comments = fetchJson(`repos/${org}/${repo}/pulls/${prNumber}/comments`);
2476
2611
  return comments.map(
2477
- (c) => {
2478
- const threadId = threadInfo.threadMap.get(c.id) ?? "";
2479
- return {
2480
- type: "line",
2481
- id: c.id,
2482
- threadId,
2483
- user: c.user.login,
2484
- path: c.path,
2485
- line: c.line,
2486
- body: c.body,
2487
- diff_hunk: c.diff_hunk,
2488
- html_url: c.html_url,
2489
- resolved: threadInfo.resolvedThreadIds.has(threadId)
2490
- };
2491
- }
2612
+ (c) => mapLineComment(c, threadInfo)
2492
2613
  );
2493
2614
  }
2494
2615
 
2495
2616
  // src/commands/prs/listComments/formatForHuman.ts
2496
- import chalk33 from "chalk";
2617
+ import chalk34 from "chalk";
2497
2618
  function formatForHuman(comment) {
2498
2619
  if (comment.type === "review") {
2499
- const stateColor = comment.state === "APPROVED" ? chalk33.green : comment.state === "CHANGES_REQUESTED" ? chalk33.red : chalk33.yellow;
2620
+ const stateColor = comment.state === "APPROVED" ? chalk34.green : comment.state === "CHANGES_REQUESTED" ? chalk34.red : chalk34.yellow;
2500
2621
  return [
2501
- `${chalk33.cyan("Review")} by ${chalk33.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
2622
+ `${chalk34.cyan("Review")} by ${chalk34.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
2502
2623
  comment.body,
2503
2624
  ""
2504
2625
  ].join("\n");
2505
2626
  }
2506
2627
  const location = comment.line ? `:${comment.line}` : "";
2507
2628
  return [
2508
- `${chalk33.cyan("Line comment")} by ${chalk33.bold(comment.user)} on ${chalk33.dim(`${comment.path}${location}`)}`,
2509
- chalk33.dim(comment.diff_hunk.split("\n").slice(-3).join("\n")),
2629
+ `${chalk34.cyan("Line comment")} by ${chalk34.bold(comment.user)} on ${chalk34.dim(`${comment.path}${location}`)}`,
2630
+ chalk34.dim(comment.diff_hunk.split("\n").slice(-3).join("\n")),
2510
2631
  comment.body,
2511
2632
  ""
2512
2633
  ].join("\n");
2513
2634
  }
2514
2635
 
2515
2636
  // src/commands/prs/listComments/index.ts
2637
+ function formatComment(comment) {
2638
+ return isClaudeCode() ? JSON.stringify(comment) : formatForHuman(comment);
2639
+ }
2516
2640
  function printComments(comments) {
2517
2641
  if (comments.length === 0) {
2518
2642
  console.log("No comments found.");
2519
2643
  return;
2520
2644
  }
2521
- if (isClaudeCode()) {
2522
- for (const comment of comments) {
2523
- console.log(JSON.stringify(comment));
2524
- }
2525
- } else {
2526
- for (const comment of comments) {
2527
- console.log(formatForHuman(comment));
2528
- }
2645
+ for (const comment of comments) {
2646
+ console.log(formatComment(comment));
2529
2647
  }
2530
- const lineComments = comments.filter((c) => c.type === "line");
2531
- if (lineComments.length === 0) {
2648
+ if (!comments.some((c) => c.type === "line")) {
2532
2649
  console.log("No line comments to process.");
2533
2650
  }
2534
2651
  }
@@ -2545,6 +2662,25 @@ function writeCommentsCache(prNumber, comments) {
2545
2662
  const cachePath = join12(assistDir, `pr-${prNumber}-comments.yaml`);
2546
2663
  writeFileSync11(cachePath, stringify(cacheData));
2547
2664
  }
2665
+ function handleKnownErrors(error) {
2666
+ if (isGhNotInstalled(error)) {
2667
+ console.error("Error: GitHub CLI (gh) is not installed.");
2668
+ console.error("Install it from https://cli.github.com/");
2669
+ return [];
2670
+ }
2671
+ if (isNotFound(error)) {
2672
+ console.error("Error: Pull request not found.");
2673
+ return [];
2674
+ }
2675
+ return null;
2676
+ }
2677
+ function updateCache(prNumber, comments) {
2678
+ if (comments.some((c) => c.type === "line")) {
2679
+ writeCommentsCache(prNumber, comments);
2680
+ } else {
2681
+ deleteCommentsCache(prNumber);
2682
+ }
2683
+ }
2548
2684
  async function listComments() {
2549
2685
  try {
2550
2686
  const prNumber = getCurrentPrNumber();
@@ -2554,22 +2690,11 @@ async function listComments() {
2554
2690
  ...fetchReviewComments(org, repo, prNumber),
2555
2691
  ...fetchLineComments(org, repo, prNumber, threadInfo)
2556
2692
  ];
2557
- if (allComments.some((c) => c.type === "line")) {
2558
- writeCommentsCache(prNumber, allComments);
2559
- } else {
2560
- deleteCommentsCache(prNumber);
2561
- }
2693
+ updateCache(prNumber, allComments);
2562
2694
  return allComments;
2563
2695
  } catch (error) {
2564
- if (isGhNotInstalled(error)) {
2565
- console.error("Error: GitHub CLI (gh) is not installed.");
2566
- console.error("Install it from https://cli.github.com/");
2567
- return [];
2568
- }
2569
- if (isNotFound(error)) {
2570
- console.error("Error: Pull request not found.");
2571
- return [];
2572
- }
2696
+ const handled = handleKnownErrors(error);
2697
+ if (handled !== null) return handled;
2573
2698
  throw error;
2574
2699
  }
2575
2700
  }
@@ -2577,69 +2702,104 @@ async function listComments() {
2577
2702
  // src/commands/prs/prs/index.ts
2578
2703
  import { execSync as execSync15 } from "child_process";
2579
2704
 
2580
- // src/commands/prs/prs/displayPaginated.ts
2581
- import chalk34 from "chalk";
2705
+ // src/commands/prs/prs/displayPaginated/index.ts
2582
2706
  import enquirer4 from "enquirer";
2583
- var PAGE_SIZE = 10;
2707
+
2708
+ // src/commands/prs/prs/displayPaginated/printPr.ts
2709
+ import chalk35 from "chalk";
2710
+ var STATUS_MAP = {
2711
+ MERGED: (pr) => pr.mergedAt ? { label: chalk35.magenta("merged"), date: pr.mergedAt } : null,
2712
+ CLOSED: (pr) => pr.closedAt ? { label: chalk35.red("closed"), date: pr.closedAt } : null
2713
+ };
2714
+ function defaultStatus(pr) {
2715
+ return { label: chalk35.green("opened"), date: pr.createdAt };
2716
+ }
2584
2717
  function getStatus(pr) {
2585
- if (pr.state === "MERGED" && pr.mergedAt) {
2586
- return { label: chalk34.magenta("merged"), date: pr.mergedAt };
2587
- }
2588
- if (pr.state === "CLOSED" && pr.closedAt) {
2589
- return { label: chalk34.red("closed"), date: pr.closedAt };
2590
- }
2591
- return { label: chalk34.green("opened"), date: pr.createdAt };
2718
+ return STATUS_MAP[pr.state]?.(pr) ?? defaultStatus(pr);
2592
2719
  }
2593
- function displayPage(pullRequests, totalPages, page) {
2594
- const start = page * PAGE_SIZE;
2595
- const end = Math.min(start + PAGE_SIZE, pullRequests.length);
2596
- const pagePrs = pullRequests.slice(start, end);
2720
+ function formatDate(dateStr) {
2721
+ return new Date(dateStr).toISOString().split("T")[0];
2722
+ }
2723
+ function formatPrHeader(pr, status) {
2724
+ return `${chalk35.cyan(`#${pr.number}`)} ${pr.title} ${chalk35.dim(`(${pr.author.login},`)} ${status.label} ${chalk35.dim(`${formatDate(status.date)})`)}`;
2725
+ }
2726
+ function logPrDetails(pr) {
2597
2727
  console.log(
2598
- `
2599
- Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
2600
- `
2728
+ chalk35.dim(` ${pr.changedFiles.toLocaleString()} files | ${pr.url}`)
2601
2729
  );
2602
- for (const pr of pagePrs) {
2603
- const status = getStatus(pr);
2604
- const formattedDate = new Date(status.date).toISOString().split("T")[0];
2605
- const fileCount = pr.changedFiles.toLocaleString();
2606
- console.log(
2607
- `${chalk34.cyan(`#${pr.number}`)} ${pr.title} ${chalk34.dim(`(${pr.author.login},`)} ${status.label} ${chalk34.dim(`${formattedDate})`)}`
2608
- );
2609
- console.log(chalk34.dim(` ${fileCount} files | ${pr.url}`));
2610
- console.log();
2730
+ console.log();
2731
+ }
2732
+ function printPr(pr) {
2733
+ console.log(formatPrHeader(pr, getStatus(pr)));
2734
+ logPrDetails(pr);
2735
+ }
2736
+
2737
+ // src/commands/prs/prs/displayPaginated/index.ts
2738
+ var PAGE_SIZE = 10;
2739
+ function getPageSlice(pullRequests, page) {
2740
+ const start = page * PAGE_SIZE;
2741
+ return pullRequests.slice(start, start + PAGE_SIZE);
2742
+ }
2743
+ function pageHeader(page, totalPages, total) {
2744
+ return `
2745
+ Page ${page + 1} of ${totalPages} (${total} total)
2746
+ `;
2747
+ }
2748
+ function displayPage(pullRequests, totalPages, page) {
2749
+ console.log(pageHeader(page, totalPages, pullRequests.length));
2750
+ for (const pr of getPageSlice(pullRequests, page)) printPr(pr);
2751
+ }
2752
+ function hasNextPage(page, total) {
2753
+ return page < total - 1;
2754
+ }
2755
+ var NEXT_CHOICE = { name: "Next page", value: "next" };
2756
+ var PREV_CHOICE = { name: "Previous page", value: "prev" };
2757
+ function getOptionalChoices(currentPage, totalPages) {
2758
+ const choices = [];
2759
+ if (hasNextPage(currentPage, totalPages)) choices.push(NEXT_CHOICE);
2760
+ if (currentPage > 0) choices.push(PREV_CHOICE);
2761
+ return choices;
2762
+ }
2763
+ function buildNavChoices(currentPage, totalPages) {
2764
+ return [
2765
+ ...getOptionalChoices(currentPage, totalPages),
2766
+ { name: "Quit", value: "quit" }
2767
+ ];
2768
+ }
2769
+ function parseAction(action) {
2770
+ if (action === "Next page") return 1;
2771
+ if (action === "Previous page") return -1;
2772
+ return 0;
2773
+ }
2774
+ async function promptNavigation(currentPage, totalPages) {
2775
+ const choices = buildNavChoices(currentPage, totalPages);
2776
+ const { action } = await enquirer4.prompt({
2777
+ type: "select",
2778
+ name: "action",
2779
+ message: "Navigate",
2780
+ choices
2781
+ });
2782
+ return parseAction(action);
2783
+ }
2784
+ function computeTotalPages(count) {
2785
+ return Math.ceil(count / PAGE_SIZE);
2786
+ }
2787
+ async function navigateAndDisplay(pullRequests, totalPages, currentPage) {
2788
+ const delta = await promptNavigation(currentPage, totalPages);
2789
+ if (delta === 0) return null;
2790
+ const next2 = currentPage + delta;
2791
+ displayPage(pullRequests, totalPages, next2);
2792
+ return next2;
2793
+ }
2794
+ async function paginationLoop(pullRequests, totalPages) {
2795
+ let page = 0;
2796
+ displayPage(pullRequests, totalPages, page);
2797
+ while (totalPages > 1 && page !== null) {
2798
+ page = await navigateAndDisplay(pullRequests, totalPages, page);
2611
2799
  }
2612
2800
  }
2613
2801
  async function displayPaginated(pullRequests) {
2614
- const totalPages = Math.ceil(pullRequests.length / PAGE_SIZE);
2615
- let currentPage = 0;
2616
- displayPage(pullRequests, totalPages, currentPage);
2617
- if (totalPages <= 1) {
2618
- return;
2619
- }
2620
- while (true) {
2621
- const hasNext = currentPage < totalPages - 1;
2622
- const hasPrev = currentPage > 0;
2623
- const choices = [];
2624
- if (hasNext) choices.push({ name: "Next page", value: "next" });
2625
- if (hasPrev) choices.push({ name: "Previous page", value: "prev" });
2626
- choices.push({ name: "Quit", value: "quit" });
2627
- const { action } = await enquirer4.prompt({
2628
- type: "select",
2629
- name: "action",
2630
- message: "Navigate",
2631
- choices
2632
- });
2633
- if (action === "Next page") {
2634
- currentPage++;
2635
- displayPage(pullRequests, totalPages, currentPage);
2636
- } else if (action === "Previous page") {
2637
- currentPage--;
2638
- displayPage(pullRequests, totalPages, currentPage);
2639
- } else {
2640
- break;
2641
- }
2642
- }
2802
+ await paginationLoop(pullRequests, computeTotalPages(pullRequests.length));
2643
2803
  }
2644
2804
 
2645
2805
  // src/commands/prs/prs/index.ts
@@ -2732,7 +2892,7 @@ import { spawn as spawn2 } from "child_process";
2732
2892
  import * as path15 from "path";
2733
2893
 
2734
2894
  // src/commands/refactor/logViolations.ts
2735
- import chalk35 from "chalk";
2895
+ import chalk36 from "chalk";
2736
2896
  var DEFAULT_MAX_LINES = 100;
2737
2897
  function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
2738
2898
  if (violations.length === 0) {
@@ -2741,43 +2901,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
2741
2901
  }
2742
2902
  return;
2743
2903
  }
2744
- console.error(chalk35.red(`
2904
+ console.error(chalk36.red(`
2745
2905
  Refactor check failed:
2746
2906
  `));
2747
- console.error(chalk35.red(` The following files exceed ${maxLines} lines:
2907
+ console.error(chalk36.red(` The following files exceed ${maxLines} lines:
2748
2908
  `));
2749
2909
  for (const violation of violations) {
2750
- console.error(chalk35.red(` ${violation.file} (${violation.lines} lines)`));
2910
+ console.error(chalk36.red(` ${violation.file} (${violation.lines} lines)`));
2751
2911
  }
2752
2912
  console.error(
2753
- chalk35.yellow(
2913
+ chalk36.yellow(
2754
2914
  `
2755
2915
  Each file needs to be sensibly refactored, or if there is no sensible
2756
2916
  way to refactor it, ignore it with:
2757
2917
  `
2758
2918
  )
2759
2919
  );
2760
- console.error(chalk35.gray(` assist refactor ignore <file>
2920
+ console.error(chalk36.gray(` assist refactor ignore <file>
2761
2921
  `));
2762
2922
  if (process.env.CLAUDECODE) {
2763
- console.error(chalk35.cyan(`
2923
+ console.error(chalk36.cyan(`
2764
2924
  ## Extracting Code to New Files
2765
2925
  `));
2766
2926
  console.error(
2767
- chalk35.cyan(
2927
+ chalk36.cyan(
2768
2928
  ` When extracting logic from one file to another, consider where the extracted code belongs:
2769
2929
  `
2770
2930
  )
2771
2931
  );
2772
2932
  console.error(
2773
- chalk35.cyan(
2933
+ chalk36.cyan(
2774
2934
  ` 1. Keep related logic together: If the extracted code is tightly coupled to the
2775
2935
  original file's domain, create a new folder containing both the original and extracted files.
2776
2936
  `
2777
2937
  )
2778
2938
  );
2779
2939
  console.error(
2780
- chalk35.cyan(
2940
+ chalk36.cyan(
2781
2941
  ` 2. Share common utilities: If the extracted code can be reused across multiple
2782
2942
  domains, move it to a common/shared folder.
2783
2943
  `
@@ -2874,46 +3034,45 @@ function getViolations(pattern2, options = {}, maxLines = DEFAULT_MAX_LINES) {
2874
3034
  }
2875
3035
 
2876
3036
  // src/commands/refactor/check/index.ts
3037
+ function runScript(script, cwd) {
3038
+ return new Promise((resolve) => {
3039
+ const child = spawn2("npm", ["run", script], {
3040
+ stdio: "pipe",
3041
+ shell: true,
3042
+ cwd
3043
+ });
3044
+ let output = "";
3045
+ child.stdout?.on("data", (data) => {
3046
+ output += data.toString();
3047
+ });
3048
+ child.stderr?.on("data", (data) => {
3049
+ output += data.toString();
3050
+ });
3051
+ child.on("close", (code) => {
3052
+ resolve({ script, code: code ?? 1, output });
3053
+ });
3054
+ });
3055
+ }
3056
+ function logFailures(failed) {
3057
+ for (const f of failed) {
3058
+ console.error(f.output);
3059
+ }
3060
+ console.error(`
3061
+ ${failed.length} verify script(s) failed:`);
3062
+ for (const f of failed) {
3063
+ console.error(` - ${f.script} (exit code ${f.code})`);
3064
+ }
3065
+ }
2877
3066
  async function runVerifyQuietly() {
2878
3067
  const result = findPackageJsonWithVerifyScripts(process.cwd());
2879
- if (!result) {
2880
- return true;
2881
- }
2882
- const { packageJsonPath, verifyScripts } = result;
2883
- const packageDir = path15.dirname(packageJsonPath);
3068
+ if (!result) return true;
3069
+ const packageDir = path15.dirname(result.packageJsonPath);
2884
3070
  const results = await Promise.all(
2885
- verifyScripts.map(
2886
- (script) => new Promise(
2887
- (resolve) => {
2888
- const child = spawn2("npm", ["run", script], {
2889
- stdio: "pipe",
2890
- shell: true,
2891
- cwd: packageDir
2892
- });
2893
- let output = "";
2894
- child.stdout?.on("data", (data) => {
2895
- output += data.toString();
2896
- });
2897
- child.stderr?.on("data", (data) => {
2898
- output += data.toString();
2899
- });
2900
- child.on("close", (code) => {
2901
- resolve({ script, code: code ?? 1, output });
2902
- });
2903
- }
2904
- )
2905
- )
3071
+ result.verifyScripts.map((script) => runScript(script, packageDir))
2906
3072
  );
2907
3073
  const failed = results.filter((r) => r.code !== 0);
2908
3074
  if (failed.length > 0) {
2909
- for (const f of failed) {
2910
- console.error(f.output);
2911
- }
2912
- console.error(`
2913
- ${failed.length} verify script(s) failed:`);
2914
- for (const f of failed) {
2915
- console.error(` - ${f.script} (exit code ${f.code})`);
2916
- }
3075
+ logFailures(failed);
2917
3076
  return false;
2918
3077
  }
2919
3078
  return true;
@@ -2934,11 +3093,11 @@ async function check(pattern2, options) {
2934
3093
 
2935
3094
  // src/commands/refactor/ignore.ts
2936
3095
  import fs16 from "fs";
2937
- import chalk36 from "chalk";
3096
+ import chalk37 from "chalk";
2938
3097
  var REFACTOR_YML_PATH2 = "refactor.yml";
2939
3098
  function ignore(file) {
2940
3099
  if (!fs16.existsSync(file)) {
2941
- console.error(chalk36.red(`Error: File does not exist: ${file}`));
3100
+ console.error(chalk37.red(`Error: File does not exist: ${file}`));
2942
3101
  process.exit(1);
2943
3102
  }
2944
3103
  const content = fs16.readFileSync(file, "utf-8");
@@ -2954,15 +3113,15 @@ function ignore(file) {
2954
3113
  fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
2955
3114
  }
2956
3115
  console.log(
2957
- chalk36.green(
3116
+ chalk37.green(
2958
3117
  `Added ${file} to refactor ignore list (max ${maxLines} lines)`
2959
3118
  )
2960
3119
  );
2961
3120
  }
2962
3121
 
2963
3122
  // src/commands/refactor/restructure/index.ts
2964
- import path23 from "path";
2965
- import chalk39 from "chalk";
3123
+ import path24 from "path";
3124
+ import chalk40 from "chalk";
2966
3125
 
2967
3126
  // src/commands/refactor/restructure/buildImportGraph/index.ts
2968
3127
  import path16 from "path";
@@ -2987,24 +3146,32 @@ function getImportSpecifiers(sourceFile) {
2987
3146
  }
2988
3147
 
2989
3148
  // src/commands/refactor/restructure/buildImportGraph/index.ts
2990
- function loadCompilerOptions(tsConfigPath) {
3149
+ function loadParsedConfig(tsConfigPath) {
2991
3150
  const configFile = ts7.readConfigFile(tsConfigPath, ts7.sys.readFile);
2992
- const parsed = ts7.parseJsonConfigFileContent(
3151
+ return ts7.parseJsonConfigFileContent(
2993
3152
  configFile.config,
2994
3153
  ts7.sys,
2995
3154
  path16.dirname(tsConfigPath)
2996
3155
  );
2997
- return parsed.options;
3156
+ }
3157
+ function addToSetMap(map, key, value) {
3158
+ let set = map.get(key);
3159
+ if (!set) {
3160
+ set = /* @__PURE__ */ new Set();
3161
+ map.set(key, set);
3162
+ }
3163
+ set.add(value);
3164
+ }
3165
+ function resolveImport(specifier, filePath, options) {
3166
+ if (!specifier.startsWith(".")) return null;
3167
+ const resolved = ts7.resolveModuleName(specifier, filePath, options, ts7.sys);
3168
+ const resolvedPath = resolved.resolvedModule?.resolvedFileName;
3169
+ if (!resolvedPath || resolvedPath.includes("node_modules")) return null;
3170
+ return path16.resolve(resolvedPath);
2998
3171
  }
2999
3172
  function buildImportGraph(candidateFiles, tsConfigPath) {
3000
- const options = loadCompilerOptions(tsConfigPath);
3001
- const configFile = ts7.readConfigFile(tsConfigPath, ts7.sys.readFile);
3002
- const parsed = ts7.parseJsonConfigFileContent(
3003
- configFile.config,
3004
- ts7.sys,
3005
- path16.dirname(tsConfigPath)
3006
- );
3007
- const program2 = ts7.createProgram(parsed.fileNames, options);
3173
+ const parsed = loadParsedConfig(tsConfigPath);
3174
+ const program2 = ts7.createProgram(parsed.fileNames, parsed.options);
3008
3175
  const edges = [];
3009
3176
  const importedBy = /* @__PURE__ */ new Map();
3010
3177
  const imports = /* @__PURE__ */ new Map();
@@ -3012,23 +3179,11 @@ function buildImportGraph(candidateFiles, tsConfigPath) {
3012
3179
  const filePath = path16.resolve(sourceFile.fileName);
3013
3180
  if (filePath.includes("node_modules")) continue;
3014
3181
  for (const specifier of getImportSpecifiers(sourceFile)) {
3015
- if (!specifier.startsWith(".")) continue;
3016
- const resolved = ts7.resolveModuleName(
3017
- specifier,
3018
- filePath,
3019
- options,
3020
- ts7.sys
3021
- );
3022
- const resolvedPath = resolved.resolvedModule?.resolvedFileName;
3023
- if (!resolvedPath || resolvedPath.includes("node_modules")) continue;
3024
- const absTarget = path16.resolve(resolvedPath);
3182
+ const absTarget = resolveImport(specifier, filePath, parsed.options);
3183
+ if (!absTarget) continue;
3025
3184
  edges.push({ source: filePath, target: absTarget, specifier });
3026
- const targetSet = importedBy.get(absTarget) ?? /* @__PURE__ */ new Set();
3027
- if (!importedBy.has(absTarget)) importedBy.set(absTarget, targetSet);
3028
- targetSet.add(filePath);
3029
- const sourceSet = imports.get(filePath) ?? /* @__PURE__ */ new Set();
3030
- if (!imports.has(filePath)) imports.set(filePath, sourceSet);
3031
- sourceSet.add(absTarget);
3185
+ addToSetMap(importedBy, absTarget, filePath);
3186
+ addToSetMap(imports, filePath, absTarget);
3032
3187
  }
3033
3188
  }
3034
3189
  return { files: candidateFiles, edges, importedBy, imports };
@@ -3109,109 +3264,150 @@ function clusterFiles(graph) {
3109
3264
  return clusters;
3110
3265
  }
3111
3266
 
3112
- // src/commands/refactor/restructure/computeRewrites.ts
3113
- import fs17 from "fs";
3267
+ // src/commands/refactor/restructure/computeRewrites/index.ts
3114
3268
  import path19 from "path";
3115
- function computeRewrites(moves, edges, allProjectFiles) {
3116
- const moveMap = /* @__PURE__ */ new Map();
3117
- for (const move of moves) {
3118
- moveMap.set(move.from, move.to);
3119
- }
3120
- const rewrites = [];
3121
- for (const file of allProjectFiles) {
3122
- const newFile = moveMap.get(file) ?? file;
3123
- const edgesFromFile = edges.filter((e) => e.source === file);
3124
- for (const edge of edgesFromFile) {
3125
- const newTarget = moveMap.get(edge.target);
3126
- if (!newTarget && !moveMap.has(file)) continue;
3127
- const targetPath = newTarget ?? edge.target;
3128
- const newSpecifier = computeSpecifier(newFile, targetPath);
3129
- if (newSpecifier === edge.specifier) continue;
3130
- rewrites.push({
3131
- file,
3132
- oldSpecifier: edge.specifier,
3133
- newSpecifier
3134
- });
3135
- }
3136
- }
3137
- return rewrites;
3269
+
3270
+ // src/commands/refactor/restructure/computeRewrites/applyRewrites.ts
3271
+ import fs17 from "fs";
3272
+ function getOrCreateList(map, key) {
3273
+ const list2 = map.get(key) ?? [];
3274
+ if (!map.has(key)) map.set(key, list2);
3275
+ return list2;
3138
3276
  }
3139
- function computeSpecifier(fromFile, toFile) {
3140
- const fromDir = path19.dirname(fromFile);
3141
- let rel = path19.relative(fromDir, toFile);
3142
- rel = rel.replace(/\\/g, "/");
3143
- rel = rel.replace(/\.tsx?$/, "");
3144
- if (rel.endsWith("/index")) {
3145
- rel = rel.slice(0, -"/index".length);
3277
+ function groupByFile(rewrites) {
3278
+ const grouped = /* @__PURE__ */ new Map();
3279
+ for (const rewrite of rewrites) {
3280
+ getOrCreateList(grouped, rewrite.file).push(rewrite);
3146
3281
  }
3147
- if (!rel.startsWith(".")) {
3148
- rel = `./${rel}`;
3282
+ return grouped;
3283
+ }
3284
+ function rewriteSpecifier(content, oldSpecifier, newSpecifier) {
3285
+ const escaped = oldSpecifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3286
+ const pattern2 = new RegExp(`(from\\s+["'])${escaped}(["'])`, "g");
3287
+ return content.replace(pattern2, `$1${newSpecifier}$2`);
3288
+ }
3289
+ function applyFileRewrites(file, fileRewrites) {
3290
+ let content = fs17.readFileSync(file, "utf-8");
3291
+ for (const { oldSpecifier, newSpecifier } of fileRewrites) {
3292
+ content = rewriteSpecifier(content, oldSpecifier, newSpecifier);
3149
3293
  }
3150
- return rel;
3294
+ return content;
3151
3295
  }
3152
3296
  function applyRewrites(rewrites) {
3153
- const fileRewrites = /* @__PURE__ */ new Map();
3154
- for (const rewrite of rewrites) {
3155
- const existing = fileRewrites.get(rewrite.file) ?? [];
3156
- if (!fileRewrites.has(rewrite.file))
3157
- fileRewrites.set(rewrite.file, existing);
3158
- existing.push(rewrite);
3159
- }
3160
3297
  const updatedContents = /* @__PURE__ */ new Map();
3161
- for (const [file, fileSpecificRewrites] of fileRewrites) {
3162
- let content = fs17.readFileSync(file, "utf-8");
3163
- for (const { oldSpecifier, newSpecifier } of fileSpecificRewrites) {
3164
- const escaped = oldSpecifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3165
- const pattern2 = new RegExp(`(from\\s+["'])${escaped}(["'])`, "g");
3166
- content = content.replace(pattern2, `$1${newSpecifier}$2`);
3167
- }
3168
- updatedContents.set(file, content);
3298
+ for (const [file, fileRewrites] of groupByFile(rewrites)) {
3299
+ updatedContents.set(file, applyFileRewrites(file, fileRewrites));
3169
3300
  }
3170
3301
  return updatedContents;
3171
3302
  }
3172
3303
 
3304
+ // src/commands/refactor/restructure/computeRewrites/index.ts
3305
+ function buildMoveMap(moves) {
3306
+ const map = /* @__PURE__ */ new Map();
3307
+ for (const move of moves) map.set(move.from, move.to);
3308
+ return map;
3309
+ }
3310
+ function stripTrailingIndex(specifier) {
3311
+ return specifier.endsWith("/index") ? specifier.slice(0, -"/index".length) : specifier;
3312
+ }
3313
+ function ensureRelative(specifier) {
3314
+ return specifier.startsWith(".") ? specifier : `./${specifier}`;
3315
+ }
3316
+ function normalizeSpecifier(rel) {
3317
+ return ensureRelative(
3318
+ stripTrailingIndex(rel.replace(/\\/g, "/").replace(/\.tsx?$/, ""))
3319
+ );
3320
+ }
3321
+ function computeSpecifier(fromFile, toFile) {
3322
+ return normalizeSpecifier(path19.relative(path19.dirname(fromFile), toFile));
3323
+ }
3324
+ function isAffected(edge, moveMap) {
3325
+ return moveMap.has(edge.target) || moveMap.has(edge.source);
3326
+ }
3327
+ function resolveTarget(edge, moveMap) {
3328
+ return moveMap.get(edge.target) ?? edge.target;
3329
+ }
3330
+ function createRewrite(edge, newSpecifier) {
3331
+ return { file: edge.source, oldSpecifier: edge.specifier, newSpecifier };
3332
+ }
3333
+ function rewriteIfChanged(edge, newSpecifier) {
3334
+ return newSpecifier === edge.specifier ? null : createRewrite(edge, newSpecifier);
3335
+ }
3336
+ function rewriteEdge(edge, newFile, moveMap) {
3337
+ if (!isAffected(edge, moveMap)) return null;
3338
+ return rewriteIfChanged(
3339
+ edge,
3340
+ computeSpecifier(newFile, resolveTarget(edge, moveMap))
3341
+ );
3342
+ }
3343
+ function fileEdges(edges, file) {
3344
+ return edges.filter((e) => e.source === file);
3345
+ }
3346
+ function collectRewrites(edges, newFile, moveMap) {
3347
+ const rewrites = [];
3348
+ for (const edge of edges) {
3349
+ const rewrite = rewriteEdge(edge, newFile, moveMap);
3350
+ if (rewrite) rewrites.push(rewrite);
3351
+ }
3352
+ return rewrites;
3353
+ }
3354
+ function rewriteEdgesForFile(file, edges, moveMap) {
3355
+ const newFile = moveMap.get(file) ?? file;
3356
+ return collectRewrites(fileEdges(edges, file), newFile, moveMap);
3357
+ }
3358
+ function computeRewrites(moves, edges, allProjectFiles) {
3359
+ const moveMap = buildMoveMap(moves);
3360
+ return [...allProjectFiles].flatMap(
3361
+ (file) => rewriteEdgesForFile(file, edges, moveMap)
3362
+ );
3363
+ }
3364
+
3173
3365
  // src/commands/refactor/restructure/displayPlan.ts
3174
3366
  import path20 from "path";
3175
- import chalk37 from "chalk";
3176
- function displayPlan(plan) {
3177
- if (plan.warnings.length > 0) {
3178
- console.log(chalk37.yellow("\nWarnings:"));
3179
- for (const warning of plan.warnings) {
3180
- console.log(chalk37.yellow(` ${warning}`));
3181
- }
3367
+ import chalk38 from "chalk";
3368
+ function relPath(filePath) {
3369
+ return path20.relative(process.cwd(), filePath);
3370
+ }
3371
+ function displayMoves(plan) {
3372
+ if (plan.moves.length === 0) return;
3373
+ console.log(chalk38.bold("\nFile moves:"));
3374
+ for (const move of plan.moves) {
3375
+ console.log(
3376
+ ` ${chalk38.red(relPath(move.from))} \u2192 ${chalk38.green(relPath(move.to))}`
3377
+ );
3378
+ console.log(chalk38.dim(` ${move.reason}`));
3182
3379
  }
3183
- if (plan.newDirectories.length > 0) {
3184
- console.log(chalk37.bold("\nNew directories:"));
3185
- for (const dir of plan.newDirectories) {
3186
- console.log(chalk37.green(` ${dir}/`));
3380
+ }
3381
+ function displayRewrites(rewrites) {
3382
+ if (rewrites.length === 0) return;
3383
+ const affectedFiles = new Set(rewrites.map((r) => r.file));
3384
+ console.log(chalk38.bold(`
3385
+ Import rewrites (${affectedFiles.size} files):`));
3386
+ for (const file of affectedFiles) {
3387
+ console.log(` ${chalk38.cyan(relPath(file))}:`);
3388
+ for (const { oldSpecifier, newSpecifier } of rewrites.filter(
3389
+ (r) => r.file === file
3390
+ )) {
3391
+ console.log(
3392
+ ` ${chalk38.red(`"${oldSpecifier}"`)} \u2192 ${chalk38.green(`"${newSpecifier}"`)}`
3393
+ );
3187
3394
  }
3188
3395
  }
3189
- if (plan.moves.length > 0) {
3190
- console.log(chalk37.bold("\nFile moves:"));
3191
- for (const move of plan.moves) {
3192
- const fromRel = path20.relative(process.cwd(), move.from);
3193
- const toRel = path20.relative(process.cwd(), move.to);
3194
- console.log(` ${chalk37.red(fromRel)} \u2192 ${chalk37.green(toRel)}`);
3195
- console.log(chalk37.dim(` ${move.reason}`));
3196
- }
3396
+ }
3397
+ function displayPlan(plan) {
3398
+ if (plan.warnings.length > 0) {
3399
+ console.log(chalk38.yellow("\nWarnings:"));
3400
+ for (const w of plan.warnings) console.log(chalk38.yellow(` ${w}`));
3197
3401
  }
3198
- if (plan.rewrites.length > 0) {
3199
- const affectedFiles = new Set(plan.rewrites.map((r) => r.file));
3200
- console.log(chalk37.bold(`
3201
- Import rewrites (${affectedFiles.size} files):`));
3202
- for (const file of affectedFiles) {
3203
- const fileRewrites = plan.rewrites.filter((r) => r.file === file);
3204
- const rel = path20.relative(process.cwd(), file);
3205
- console.log(` ${chalk37.cyan(rel)}:`);
3206
- for (const { oldSpecifier, newSpecifier } of fileRewrites) {
3207
- console.log(
3208
- ` ${chalk37.red(`"${oldSpecifier}"`)} \u2192 ${chalk37.green(`"${newSpecifier}"`)}`
3209
- );
3210
- }
3211
- }
3402
+ if (plan.newDirectories.length > 0) {
3403
+ console.log(chalk38.bold("\nNew directories:"));
3404
+ for (const dir of plan.newDirectories)
3405
+ console.log(chalk38.green(` ${dir}/`));
3212
3406
  }
3407
+ displayMoves(plan);
3408
+ displayRewrites(plan.rewrites);
3213
3409
  console.log(
3214
- chalk37.dim(
3410
+ chalk38.dim(
3215
3411
  `
3216
3412
  Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rewritten`
3217
3413
  )
@@ -3221,18 +3417,18 @@ Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rew
3221
3417
  // src/commands/refactor/restructure/executePlan.ts
3222
3418
  import fs18 from "fs";
3223
3419
  import path21 from "path";
3224
- import chalk38 from "chalk";
3420
+ import chalk39 from "chalk";
3225
3421
  function executePlan(plan) {
3226
3422
  const updatedContents = applyRewrites(plan.rewrites);
3227
3423
  for (const [file, content] of updatedContents) {
3228
3424
  fs18.writeFileSync(file, content, "utf-8");
3229
3425
  console.log(
3230
- chalk38.cyan(` Rewrote imports in ${path21.relative(process.cwd(), file)}`)
3426
+ chalk39.cyan(` Rewrote imports in ${path21.relative(process.cwd(), file)}`)
3231
3427
  );
3232
3428
  }
3233
3429
  for (const dir of plan.newDirectories) {
3234
3430
  fs18.mkdirSync(dir, { recursive: true });
3235
- console.log(chalk38.green(` Created ${path21.relative(process.cwd(), dir)}/`));
3431
+ console.log(chalk39.green(` Created ${path21.relative(process.cwd(), dir)}/`));
3236
3432
  }
3237
3433
  for (const move of plan.moves) {
3238
3434
  const targetDir = path21.dirname(move.to);
@@ -3241,7 +3437,7 @@ function executePlan(plan) {
3241
3437
  }
3242
3438
  fs18.renameSync(move.from, move.to);
3243
3439
  console.log(
3244
- chalk38.white(
3440
+ chalk39.white(
3245
3441
  ` Moved ${path21.relative(process.cwd(), move.from)} \u2192 ${path21.relative(process.cwd(), move.to)}`
3246
3442
  )
3247
3443
  );
@@ -3256,7 +3452,7 @@ function removeEmptyDirectories(dirs) {
3256
3452
  if (entries.length === 0) {
3257
3453
  fs18.rmdirSync(dir);
3258
3454
  console.log(
3259
- chalk38.dim(
3455
+ chalk39.dim(
3260
3456
  ` Removed empty directory ${path21.relative(process.cwd(), dir)}`
3261
3457
  )
3262
3458
  );
@@ -3264,84 +3460,110 @@ function removeEmptyDirectories(dirs) {
3264
3460
  }
3265
3461
  }
3266
3462
 
3267
- // src/commands/refactor/restructure/planFileMoves.ts
3463
+ // src/commands/refactor/restructure/planFileMoves/index.ts
3464
+ import fs20 from "fs";
3465
+ import path23 from "path";
3466
+
3467
+ // src/commands/refactor/restructure/planFileMoves/planDirectoryMoves.ts
3268
3468
  import fs19 from "fs";
3269
3469
  import path22 from "path";
3270
- function emptyResult() {
3271
- return { moves: [], directories: [], warnings: [] };
3470
+ function collectEntry(results, dir, entry) {
3471
+ const full = path22.join(dir, entry.name);
3472
+ const items = entry.isDirectory() ? listFilesRecursive(full) : [full];
3473
+ results.push(...items);
3272
3474
  }
3273
- function planFileMoves(clusters) {
3274
- const result = emptyResult();
3275
- for (const [parent, children] of clusters) {
3276
- const parentBase = path22.basename(parent, path22.extname(parent));
3277
- const newDirName = parentBase;
3278
- const newDir = path22.join(path22.dirname(parent), newDirName);
3279
- if (fs19.existsSync(newDir)) {
3280
- result.warnings.push(
3281
- `Skipping ${parent}: directory ${newDir} already exists`
3282
- );
3283
- continue;
3284
- }
3285
- result.directories.push(newDir);
3286
- result.moves.push({
3287
- from: parent,
3288
- to: path22.join(newDir, `index${path22.extname(parent)}`),
3289
- reason: `Main module of new ${newDirName}/ directory`
3290
- });
3291
- for (const child of children) {
3292
- result.moves.push({
3293
- from: child,
3294
- to: path22.join(newDir, path22.basename(child)),
3295
- reason: `Only imported by ${parentBase}`
3296
- });
3297
- }
3475
+ function listFilesRecursive(dir) {
3476
+ if (!fs19.existsSync(dir)) return [];
3477
+ const results = [];
3478
+ for (const entry of fs19.readdirSync(dir, { withFileTypes: true })) {
3479
+ collectEntry(results, dir, entry);
3298
3480
  }
3299
- return result;
3481
+ return results;
3300
3482
  }
3301
- function planDirectoryMoves(clusters) {
3302
- const result = emptyResult();
3303
- for (const [parentDir, childDirs] of clusters) {
3304
- for (const childDir of childDirs) {
3305
- const childName = path22.basename(childDir);
3306
- const newLocation = path22.join(parentDir, childName);
3307
- if (fs19.existsSync(newLocation)) {
3308
- result.warnings.push(
3309
- `Skipping ${childDir}: ${newLocation} already exists`
3310
- );
3311
- continue;
3312
- }
3313
- result.directories.push(newLocation);
3314
- const files = listFilesRecursive(childDir);
3315
- for (const file of files) {
3316
- const rel = path22.relative(childDir, file);
3317
- result.moves.push({
3318
- from: file,
3319
- to: path22.join(newLocation, rel),
3320
- reason: `Directory only imported from ${path22.basename(parentDir)}/`
3321
- });
3322
- }
3323
- }
3483
+ function addDirectoryFileMoves(moves, childDir, newLocation, reason) {
3484
+ for (const file of listFilesRecursive(childDir)) {
3485
+ const rel = path22.relative(childDir, file);
3486
+ moves.push({ from: file, to: path22.join(newLocation, rel), reason });
3324
3487
  }
3325
- return result;
3326
3488
  }
3327
- function listFilesRecursive(dir) {
3328
- const results = [];
3329
- if (!fs19.existsSync(dir)) return results;
3330
- const entries = fs19.readdirSync(dir, { withFileTypes: true });
3331
- for (const entry of entries) {
3332
- const full = path22.join(dir, entry.name);
3333
- if (entry.isDirectory()) {
3334
- results.push(...listFilesRecursive(full));
3335
- } else {
3336
- results.push(full);
3337
- }
3489
+ function resolveChildDest(parentDir, childDir) {
3490
+ return path22.join(parentDir, path22.basename(childDir));
3491
+ }
3492
+ function childMoveReason(parentDir) {
3493
+ return `Directory only imported from ${path22.basename(parentDir)}/`;
3494
+ }
3495
+ function registerDirectoryMove(result, childDir, dest, parentDir) {
3496
+ result.directories.push(dest);
3497
+ const reason = childMoveReason(parentDir);
3498
+ addDirectoryFileMoves(result.moves, childDir, dest, reason);
3499
+ }
3500
+ function planChildDirectoryMove(result, parentDir, childDir) {
3501
+ const dest = resolveChildDest(parentDir, childDir);
3502
+ if (checkDirConflict(result, childDir, dest)) return;
3503
+ registerDirectoryMove(result, childDir, dest, parentDir);
3504
+ }
3505
+ function processDirectoryCluster(result, parentDir, childDirs) {
3506
+ for (const childDir of childDirs)
3507
+ planChildDirectoryMove(result, parentDir, childDir);
3508
+ }
3509
+ function planDirectoryMoves(clusters) {
3510
+ const result = emptyResult();
3511
+ for (const [parentDir, childDirs] of clusters)
3512
+ processDirectoryCluster(result, parentDir, childDirs);
3513
+ return result;
3514
+ }
3515
+
3516
+ // src/commands/refactor/restructure/planFileMoves/index.ts
3517
+ function emptyResult() {
3518
+ return { moves: [], directories: [], warnings: [] };
3519
+ }
3520
+ function childMoveData(child, newDir, parentBase) {
3521
+ const to = path23.join(newDir, path23.basename(child));
3522
+ return { from: child, to, reason: `Only imported by ${parentBase}` };
3523
+ }
3524
+ function addChildMoves(moves, children, newDir, parentBase) {
3525
+ for (const child of children)
3526
+ moves.push(childMoveData(child, newDir, parentBase));
3527
+ }
3528
+ function checkDirConflict(result, label, dir) {
3529
+ if (!fs20.existsSync(dir)) return false;
3530
+ result.warnings.push(`Skipping ${label}: directory ${dir} already exists`);
3531
+ return true;
3532
+ }
3533
+ function getBaseName(filePath) {
3534
+ return path23.basename(filePath, path23.extname(filePath));
3535
+ }
3536
+ function resolveClusterDir(parent) {
3537
+ return path23.join(path23.dirname(parent), getBaseName(parent));
3538
+ }
3539
+ function createParentMove(parent, newDir) {
3540
+ return {
3541
+ from: parent,
3542
+ to: path23.join(newDir, `index${path23.extname(parent)}`),
3543
+ reason: `Main module of new ${getBaseName(parent)}/ directory`
3544
+ };
3545
+ }
3546
+ function registerClusterMoves(result, parent, newDir, children) {
3547
+ result.directories.push(newDir);
3548
+ result.moves.push(createParentMove(parent, newDir));
3549
+ addChildMoves(result.moves, children, newDir, getBaseName(parent));
3550
+ }
3551
+ function planClusterMoves(result, parent, children) {
3552
+ const newDir = resolveClusterDir(parent);
3553
+ if (checkDirConflict(result, parent, newDir)) return;
3554
+ registerClusterMoves(result, parent, newDir, children);
3555
+ }
3556
+ function planFileMoves(clusters) {
3557
+ const result = emptyResult();
3558
+ for (const [parent, children] of clusters) {
3559
+ planClusterMoves(result, parent, children);
3338
3560
  }
3339
- return results;
3561
+ return result;
3340
3562
  }
3341
3563
 
3342
3564
  // src/commands/refactor/restructure/index.ts
3343
3565
  function buildPlan(candidateFiles, tsConfigPath) {
3344
- const candidates = new Set(candidateFiles.map((f) => path23.resolve(f)));
3566
+ const candidates = new Set(candidateFiles.map((f) => path24.resolve(f)));
3345
3567
  const graph = buildImportGraph(candidates, tsConfigPath);
3346
3568
  const allProjectFiles = /* @__PURE__ */ new Set([
3347
3569
  ...graph.importedBy.keys(),
@@ -3361,22 +3583,22 @@ async function restructure(pattern2, options = {}) {
3361
3583
  const targetPattern = pattern2 ?? "src";
3362
3584
  const files = findSourceFiles2(targetPattern);
3363
3585
  if (files.length === 0) {
3364
- console.log(chalk39.yellow("No files found matching pattern"));
3586
+ console.log(chalk40.yellow("No files found matching pattern"));
3365
3587
  return;
3366
3588
  }
3367
- const tsConfigPath = path23.resolve("tsconfig.json");
3589
+ const tsConfigPath = path24.resolve("tsconfig.json");
3368
3590
  const plan = buildPlan(files, tsConfigPath);
3369
3591
  if (plan.moves.length === 0) {
3370
- console.log(chalk39.green("No restructuring needed"));
3592
+ console.log(chalk40.green("No restructuring needed"));
3371
3593
  return;
3372
3594
  }
3373
3595
  displayPlan(plan);
3374
3596
  if (options.apply) {
3375
- console.log(chalk39.bold("\nApplying changes..."));
3597
+ console.log(chalk40.bold("\nApplying changes..."));
3376
3598
  executePlan(plan);
3377
- console.log(chalk39.green("\nRestructuring complete"));
3599
+ console.log(chalk40.green("\nRestructuring complete"));
3378
3600
  } else {
3379
- console.log(chalk39.dim("\nDry run. Use --apply to execute."));
3601
+ console.log(chalk40.dim("\nDry run. Use --apply to execute."));
3380
3602
  }
3381
3603
  }
3382
3604
 
@@ -3414,38 +3636,31 @@ function getDatePrefix(daysOffset = 0) {
3414
3636
  function isValidDatePrefix(filename) {
3415
3637
  return DATE_PREFIX_REGEX.test(filename);
3416
3638
  }
3417
- function findFilesRecursive(dir, baseDir, extension, createEntry) {
3418
- if (!existsSync14(dir)) {
3419
- return [];
3420
- }
3639
+ function collectFiles(dir, extension) {
3640
+ if (!existsSync14(dir)) return [];
3421
3641
  const results = [];
3422
- const entries = readdirSync2(dir);
3423
- for (const entry of entries) {
3642
+ for (const entry of readdirSync2(dir)) {
3424
3643
  const fullPath = join13(dir, entry);
3425
- const stat = statSync(fullPath);
3426
- if (stat.isDirectory()) {
3427
- results.push(
3428
- ...findFilesRecursive(fullPath, baseDir, extension, createEntry)
3429
- );
3644
+ if (statSync(fullPath).isDirectory()) {
3645
+ results.push(...collectFiles(fullPath, extension));
3430
3646
  } else if (entry.endsWith(extension)) {
3431
- results.push(createEntry(fullPath, relative(baseDir, fullPath), entry));
3647
+ results.push(fullPath);
3432
3648
  }
3433
3649
  }
3434
3650
  return results;
3435
3651
  }
3652
+ function toFileInfo(baseDir, fullPath) {
3653
+ return {
3654
+ absolutePath: fullPath,
3655
+ relativePath: relative(baseDir, fullPath),
3656
+ filename: basename3(fullPath)
3657
+ };
3658
+ }
3436
3659
  function findVttFilesRecursive(dir, baseDir = dir) {
3437
- return findFilesRecursive(dir, baseDir, ".vtt", (abs, rel, name) => ({
3438
- absolutePath: abs,
3439
- relativePath: rel,
3440
- filename: name
3441
- }));
3660
+ return collectFiles(dir, ".vtt").map((f) => toFileInfo(baseDir, f));
3442
3661
  }
3443
3662
  function findMdFilesRecursive(dir, baseDir = dir) {
3444
- return findFilesRecursive(dir, baseDir, ".md", (abs, rel, name) => ({
3445
- absolutePath: abs,
3446
- relativePath: rel,
3447
- filename: name
3448
- }));
3663
+ return collectFiles(dir, ".md").map((f) => toFileInfo(baseDir, f));
3449
3664
  }
3450
3665
  function getTranscriptBaseName(transcriptFile) {
3451
3666
  return basename3(transcriptFile, ".md").replace(/ Transcription$/, "");
@@ -3465,44 +3680,52 @@ function askQuestion(rl, question) {
3465
3680
  }
3466
3681
 
3467
3682
  // src/commands/transcript/configure.ts
3683
+ function buildPrompt(label, current) {
3684
+ return current ? `${label} [${current}]: ` : `${label}: `;
3685
+ }
3686
+ function printExisting(existing) {
3687
+ console.log("Current configuration:");
3688
+ console.log(` VTT directory: ${existing.vttDir}`);
3689
+ console.log(` Transcripts directory: ${existing.transcriptsDir}`);
3690
+ console.log(` Summary directory: ${existing.summaryDir}`);
3691
+ console.log();
3692
+ }
3693
+ async function promptDirectories(rl, existing) {
3694
+ const vttDir = await askQuestion(
3695
+ rl,
3696
+ buildPrompt("VTT directory", existing?.vttDir)
3697
+ );
3698
+ const transcriptsDir = await askQuestion(
3699
+ rl,
3700
+ buildPrompt("Transcripts directory", existing?.transcriptsDir)
3701
+ );
3702
+ const summaryDir = await askQuestion(
3703
+ rl,
3704
+ buildPrompt("Summary directory", existing?.summaryDir)
3705
+ );
3706
+ return {
3707
+ vttDir: vttDir || existing?.vttDir || "",
3708
+ transcriptsDir: transcriptsDir || existing?.transcriptsDir || "",
3709
+ summaryDir: summaryDir || existing?.summaryDir || ""
3710
+ };
3711
+ }
3712
+ function validateDirectories(transcript) {
3713
+ if (!transcript.vttDir || !transcript.transcriptsDir || !transcript.summaryDir) {
3714
+ console.error("\nError: All directories must be specified.");
3715
+ process.exit(1);
3716
+ }
3717
+ }
3468
3718
  async function configure() {
3469
3719
  const rl = createReadlineInterface();
3470
3720
  const config = loadConfig();
3721
+ const existing = config.transcript;
3471
3722
  console.log("Configure transcript directories\n");
3472
- if (config.transcript) {
3473
- console.log("Current configuration:");
3474
- console.log(` VTT directory: ${config.transcript.vttDir}`);
3475
- console.log(` Transcripts directory: ${config.transcript.transcriptsDir}`);
3476
- console.log(` Summary directory: ${config.transcript.summaryDir}`);
3477
- console.log();
3478
- }
3723
+ if (existing) printExisting(existing);
3479
3724
  try {
3480
- const vttDir = await askQuestion(
3481
- rl,
3482
- `VTT directory${config.transcript?.vttDir ? ` [${config.transcript.vttDir}]` : ""}: `
3483
- );
3484
- const transcriptsDir = await askQuestion(
3485
- rl,
3486
- `Transcripts directory${config.transcript?.transcriptsDir ? ` [${config.transcript.transcriptsDir}]` : ""}: `
3487
- );
3488
- const summaryDir = await askQuestion(
3489
- rl,
3490
- `Summary directory${config.transcript?.summaryDir ? ` [${config.transcript.summaryDir}]` : ""}: `
3491
- );
3725
+ const transcript = await promptDirectories(rl, existing);
3492
3726
  rl.close();
3493
- const newConfig = {
3494
- ...config,
3495
- transcript: {
3496
- vttDir: vttDir || config.transcript?.vttDir || "",
3497
- transcriptsDir: transcriptsDir || config.transcript?.transcriptsDir || "",
3498
- summaryDir: summaryDir || config.transcript?.summaryDir || ""
3499
- }
3500
- };
3501
- if (!newConfig.transcript.vttDir || !newConfig.transcript.transcriptsDir || !newConfig.transcript.summaryDir) {
3502
- console.error("\nError: All directories must be specified.");
3503
- process.exit(1);
3504
- }
3505
- saveConfig(newConfig);
3727
+ validateDirectories(transcript);
3728
+ saveConfig({ ...config, transcript });
3506
3729
  console.log("\nConfiguration saved.");
3507
3730
  } catch (error) {
3508
3731
  rl.close();
@@ -3511,8 +3734,7 @@ async function configure() {
3511
3734
  }
3512
3735
 
3513
3736
  // src/commands/transcript/format/index.ts
3514
- import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
3515
- import { basename as basename4, dirname as dirname11, join as join16 } from "path";
3737
+ import { existsSync as existsSync16 } from "fs";
3516
3738
 
3517
3739
  // src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
3518
3740
  import { dirname as dirname10, join as join15 } from "path";
@@ -3520,6 +3742,24 @@ import { dirname as dirname10, join as join15 } from "path";
3520
3742
  // src/commands/transcript/format/fixInvalidDatePrefixes/promptForDateFix.ts
3521
3743
  import { renameSync } from "fs";
3522
3744
  import { join as join14 } from "path";
3745
+ async function resolveDate(rl, choice) {
3746
+ if (choice === "1") return getDatePrefix(0);
3747
+ if (choice === "2") return getDatePrefix(-1);
3748
+ if (choice === "3") {
3749
+ const customDate = await askQuestion(rl, "Enter date (YYYY-MM-DD): ");
3750
+ if (/^\d{4}-\d{2}-\d{2}$/.test(customDate)) return customDate;
3751
+ console.log("Invalid date format. Cancelling.");
3752
+ return null;
3753
+ }
3754
+ console.log("Cancelled.");
3755
+ return null;
3756
+ }
3757
+ function renameWithPrefix(vttDir, vttFile, prefix) {
3758
+ const newFilename = `${prefix}.${vttFile}`;
3759
+ renameSync(join14(vttDir, vttFile), join14(vttDir, newFilename));
3760
+ console.log(`Renamed to: ${newFilename}`);
3761
+ return newFilename;
3762
+ }
3523
3763
  async function promptForDateFix(vttFile, vttDir) {
3524
3764
  const rl = createReadlineInterface();
3525
3765
  console.log(
@@ -3533,40 +3773,9 @@ Error: File "${vttFile}" does not start with YYYY-MM-DD format.`
3533
3773
  console.log(" 4. Cancel");
3534
3774
  try {
3535
3775
  const choice = await askQuestion(rl, "\nSelect an option (1/2/3/4): ");
3536
- let newPrefix = null;
3537
- switch (choice) {
3538
- case "1":
3539
- newPrefix = getDatePrefix(0);
3540
- break;
3541
- case "2":
3542
- newPrefix = getDatePrefix(-1);
3543
- break;
3544
- case "3": {
3545
- const customDate = await askQuestion(rl, "Enter date (YYYY-MM-DD): ");
3546
- if (/^\d{4}-\d{2}-\d{2}$/.test(customDate)) {
3547
- newPrefix = customDate;
3548
- } else {
3549
- console.log("Invalid date format. Cancelling.");
3550
- rl.close();
3551
- return null;
3552
- }
3553
- break;
3554
- }
3555
- default:
3556
- console.log("Cancelled.");
3557
- rl.close();
3558
- return null;
3559
- }
3776
+ const prefix = await resolveDate(rl, choice);
3560
3777
  rl.close();
3561
- if (newPrefix) {
3562
- const newFilename = `${newPrefix}.${vttFile}`;
3563
- const oldPath = join14(vttDir, vttFile);
3564
- const newPath = join14(vttDir, newFilename);
3565
- renameSync(oldPath, newPath);
3566
- console.log(`Renamed to: ${newFilename}`);
3567
- return newFilename;
3568
- }
3569
- return null;
3778
+ return prefix ? renameWithPrefix(vttDir, vttFile, prefix) : null;
3570
3779
  } catch (error) {
3571
3780
  rl.close();
3572
3781
  throw error;
@@ -3598,6 +3807,10 @@ async function fixInvalidDatePrefixes(vttFiles) {
3598
3807
  return vttFiles.filter((f) => f.absolutePath !== "");
3599
3808
  }
3600
3809
 
3810
+ // src/commands/transcript/format/processVttFile/index.ts
3811
+ import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync12, writeFileSync as writeFileSync12 } from "fs";
3812
+ import { basename as basename4, dirname as dirname11, join as join16 } from "path";
3813
+
3601
3814
  // src/commands/transcript/cleanText.ts
3602
3815
  function cleanText(text) {
3603
3816
  const words = text.split(/\s+/);
@@ -3619,59 +3832,81 @@ function cleanText(text) {
3619
3832
  return cleaned.join(" ").replace(/\s+/g, " ").trim();
3620
3833
  }
3621
3834
 
3622
- // src/commands/transcript/format/parseVtt/deduplicateCues.ts
3835
+ // src/commands/transcript/format/parseVtt/deduplicateCues/removeSubstringDuplicates.ts
3836
+ function normalizeText(text) {
3837
+ return text.toLowerCase().trim();
3838
+ }
3839
+ function checkSubstringRelation(textI, textJ) {
3840
+ if (textI.includes(textJ) && textI.length > textJ.length)
3841
+ return "i-contains-j";
3842
+ if (textJ.includes(textI) && textJ.length > textI.length)
3843
+ return "j-contains-i";
3844
+ return null;
3845
+ }
3846
+ function isNearby(a, b) {
3847
+ return b.startMs - a.endMs <= 1e4;
3848
+ }
3849
+ function comparePair(cueI, cueJ) {
3850
+ if (cueI.speaker !== cueJ.speaker) return null;
3851
+ const relation = checkSubstringRelation(
3852
+ normalizeText(cueI.text),
3853
+ normalizeText(cueJ.text)
3854
+ );
3855
+ if (relation === "i-contains-j") return "remove-j";
3856
+ if (relation === "j-contains-i") return "remove-i";
3857
+ return null;
3858
+ }
3859
+ function scanForward(cues, i, toRemove) {
3860
+ for (let j = i + 1; j < cues.length; j++) {
3861
+ if (toRemove.has(j)) continue;
3862
+ if (!isNearby(cues[i], cues[j])) break;
3863
+ const action = comparePair(cues[i], cues[j]);
3864
+ if (action === "remove-j") toRemove.add(j);
3865
+ else if (action === "remove-i") return true;
3866
+ }
3867
+ return false;
3868
+ }
3623
3869
  function removeSubstringDuplicates(cues) {
3624
3870
  const toRemove = /* @__PURE__ */ new Set();
3625
3871
  for (let i = 0; i < cues.length; i++) {
3626
3872
  if (toRemove.has(i)) continue;
3627
- for (let j = i + 1; j < cues.length; j++) {
3628
- if (toRemove.has(j)) continue;
3629
- if (cues[j].startMs - cues[i].endMs > 1e4) break;
3630
- if (cues[i].speaker !== cues[j].speaker) continue;
3631
- const textI = cues[i].text.toLowerCase().trim();
3632
- const textJ = cues[j].text.toLowerCase().trim();
3633
- if (textI.includes(textJ) && textI.length > textJ.length) {
3634
- toRemove.add(j);
3635
- } else if (textJ.includes(textI) && textJ.length > textI.length) {
3636
- toRemove.add(i);
3637
- break;
3638
- }
3639
- }
3873
+ if (scanForward(cues, i, toRemove)) toRemove.add(i);
3640
3874
  }
3641
3875
  return cues.filter((_, i) => !toRemove.has(i));
3642
3876
  }
3877
+
3878
+ // src/commands/transcript/format/parseVtt/deduplicateCues/index.ts
3643
3879
  function findWordOverlap(currentWords, nextWords) {
3644
3880
  for (let j = Math.min(5, currentWords.length); j >= 1; j--) {
3645
3881
  const suffix = currentWords.slice(-j).join(" ");
3646
3882
  const prefix = nextWords.slice(0, j).join(" ");
3647
- if (suffix === prefix) {
3648
- return j;
3649
- }
3883
+ if (suffix === prefix) return j;
3650
3884
  }
3651
3885
  return 0;
3652
3886
  }
3887
+ function joinWithOverlap(currentText, nextText) {
3888
+ const currentWords = currentText.toLowerCase().split(/\s+/);
3889
+ const nextWords = nextText.toLowerCase().split(/\s+/);
3890
+ const overlapIndex = findWordOverlap(currentWords, nextWords);
3891
+ if (overlapIndex > 0) {
3892
+ return `${currentText} ${nextText.split(/\s+/).slice(overlapIndex).join(" ")}`;
3893
+ }
3894
+ return currentText.includes(nextText) ? currentText : `${currentText} ${nextText}`;
3895
+ }
3896
+ function canMergeCues(current, next2) {
3897
+ return current.speaker === next2.speaker && next2.startMs <= current.endMs + 500;
3898
+ }
3653
3899
  function mergeOverlappingCues(cues) {
3654
3900
  if (cues.length === 0) return [];
3655
3901
  const result = [];
3656
3902
  let current = { ...cues[0] };
3657
3903
  for (let i = 1; i < cues.length; i++) {
3658
- const next2 = cues[i];
3659
- const overlaps = next2.startMs <= current.endMs + 500;
3660
- const sameSpeaker = current.speaker === next2.speaker;
3661
- if (sameSpeaker && overlaps) {
3662
- const currentWords = current.text.toLowerCase().split(/\s+/);
3663
- const nextWords = next2.text.toLowerCase().split(/\s+/);
3664
- const overlapIndex = findWordOverlap(currentWords, nextWords);
3665
- if (overlapIndex > 0) {
3666
- const nextOriginalWords = next2.text.split(/\s+/);
3667
- current.text = `${current.text} ${nextOriginalWords.slice(overlapIndex).join(" ")}`;
3668
- } else if (!current.text.includes(next2.text)) {
3669
- current.text = `${current.text} ${next2.text}`;
3670
- }
3671
- current.endMs = Math.max(current.endMs, next2.endMs);
3904
+ if (canMergeCues(current, cues[i])) {
3905
+ current.text = joinWithOverlap(current.text, cues[i].text);
3906
+ current.endMs = Math.max(current.endMs, cues[i].endMs);
3672
3907
  } else {
3673
3908
  result.push(current);
3674
- current = { ...next2 };
3909
+ current = { ...cues[i] };
3675
3910
  }
3676
3911
  }
3677
3912
  result.push(current);
@@ -3689,59 +3924,76 @@ function deduplicateCues(cues) {
3689
3924
  }
3690
3925
 
3691
3926
  // src/commands/transcript/format/parseVtt/index.ts
3927
+ function parseHMS(h, m, s) {
3928
+ return Number.parseInt(h, 10) * 3600 + Number.parseInt(m, 10) * 60 + Number.parseFloat(s);
3929
+ }
3930
+ function parseMS(m, s) {
3931
+ return Number.parseInt(m, 10) * 60 + Number.parseFloat(s);
3932
+ }
3933
+ function toSeconds(parts) {
3934
+ if (parts.length === 3) return parseHMS(parts[0], parts[1], parts[2]);
3935
+ if (parts.length === 2) return parseMS(parts[0], parts[1]);
3936
+ return 0;
3937
+ }
3692
3938
  function parseTimestamp(ts8) {
3693
- const parts = ts8.split(":");
3694
- let hours = 0;
3695
- let minutes = 0;
3696
- let seconds = 0;
3697
- if (parts.length === 3) {
3698
- hours = Number.parseInt(parts[0], 10);
3699
- minutes = Number.parseInt(parts[1], 10);
3700
- seconds = Number.parseFloat(parts[2]);
3701
- } else if (parts.length === 2) {
3702
- minutes = Number.parseInt(parts[0], 10);
3703
- seconds = Number.parseFloat(parts[1]);
3704
- }
3705
- return Math.round((hours * 3600 + minutes * 60 + seconds) * 1e3);
3939
+ return Math.round(toSeconds(ts8.split(":")) * 1e3);
3940
+ }
3941
+ function extractSpeaker(fullText) {
3942
+ const match = fullText.match(/^<v\s+([^>]+)>/);
3943
+ if (!match) return { speaker: null, text: fullText };
3944
+ return {
3945
+ speaker: match[1],
3946
+ text: fullText.replace(/<v\s+[^>]+>/, "").trim()
3947
+ };
3948
+ }
3949
+ function isTextLine(line) {
3950
+ return !!line.trim() && !line.includes("-->");
3951
+ }
3952
+ function scanTextLines(lines, start) {
3953
+ let i = start;
3954
+ while (i < lines.length && isTextLine(lines[i])) i++;
3955
+ return { texts: lines.slice(start, i).map((l) => l.trim()), end: i };
3956
+ }
3957
+ function collectTextLines(lines, startIndex) {
3958
+ const { texts, end } = scanTextLines(lines, startIndex);
3959
+ return { text: texts.join(" "), nextIndex: end };
3960
+ }
3961
+ function parseTimestampLine(line) {
3962
+ const [startStr, endStr] = line.trim().split("-->").map((s) => s.trim());
3963
+ return { startMs: parseTimestamp(startStr), endMs: parseTimestamp(endStr) };
3964
+ }
3965
+ function buildCue(startMs, endMs, fullText) {
3966
+ const { speaker, text } = extractSpeaker(fullText);
3967
+ return text ? { startMs, endMs, speaker, text } : null;
3968
+ }
3969
+ function parseCueLine(lines, i) {
3970
+ const { startMs, endMs } = parseTimestampLine(lines[i]);
3971
+ const { text, nextIndex } = collectTextLines(lines, i + 1);
3972
+ return { cue: buildCue(startMs, endMs, text), nextIndex };
3973
+ }
3974
+ function isCueSeparator(line) {
3975
+ return line.trim().includes("-->");
3976
+ }
3977
+ function skipHeader(lines) {
3978
+ let i = 0;
3979
+ while (i < lines.length && !isCueSeparator(lines[i])) i++;
3980
+ return i;
3981
+ }
3982
+ function processLine(cues, lines, i) {
3983
+ if (!isCueSeparator(lines[i])) return i + 1;
3984
+ const { cue, nextIndex } = parseCueLine(lines, i);
3985
+ if (cue) cues.push(cue);
3986
+ return nextIndex;
3706
3987
  }
3707
3988
  function parseVtt(content) {
3708
3989
  const cues = [];
3709
3990
  const lines = content.split(/\r?\n/);
3710
- let i = 0;
3711
- while (i < lines.length && !lines[i].includes("-->")) {
3712
- i++;
3713
- }
3714
- while (i < lines.length) {
3715
- const line = lines[i].trim();
3716
- if (line.includes("-->")) {
3717
- const [startStr, endStr] = line.split("-->").map((s) => s.trim());
3718
- const startMs = parseTimestamp(startStr);
3719
- const endMs = parseTimestamp(endStr);
3720
- const textLines = [];
3721
- i++;
3722
- while (i < lines.length && lines[i].trim() && !lines[i].includes("-->")) {
3723
- textLines.push(lines[i].trim());
3724
- i++;
3725
- }
3726
- const fullText = textLines.join(" ");
3727
- const speakerMatch = fullText.match(/^<v\s+([^>]+)>/);
3728
- let speaker = null;
3729
- let text = fullText;
3730
- if (speakerMatch) {
3731
- speaker = speakerMatch[1];
3732
- text = fullText.replace(/<v\s+[^>]+>/, "").trim();
3733
- }
3734
- if (text) {
3735
- cues.push({ startMs, endMs, speaker, text });
3736
- }
3737
- } else {
3738
- i++;
3739
- }
3740
- }
3991
+ let i = skipHeader(lines);
3992
+ while (i < lines.length) i = processLine(cues, lines, i);
3741
3993
  return cues;
3742
3994
  }
3743
3995
 
3744
- // src/commands/transcript/format/formatChatLog.ts
3996
+ // src/commands/transcript/format/processVttFile/formatChatLog.ts
3745
3997
  function cuesToChatMessages(cues) {
3746
3998
  const messages = [];
3747
3999
  for (const cue of cues) {
@@ -3762,74 +4014,125 @@ function formatChatLog(messages) {
3762
4014
  return messages.map((msg) => `${msg.speaker}: ${msg.text}`).join("\n\n");
3763
4015
  }
3764
4016
 
3765
- // src/commands/transcript/format/index.ts
3766
- function processFile(inputPath, outputPath) {
3767
- console.log(`Reading: ${inputPath}`);
3768
- const content = readFileSync12(inputPath, "utf-8");
4017
+ // src/commands/transcript/format/processVttFile/index.ts
4018
+ function toMdFilename(vttFilename) {
4019
+ return `${basename4(vttFilename, ".vtt").replace(/\s*Transcription\s*/g, " ").trim()}.md`;
4020
+ }
4021
+ function resolveOutputDir(relativeDir, transcriptsDir) {
4022
+ return relativeDir === "." ? transcriptsDir : join16(transcriptsDir, relativeDir);
4023
+ }
4024
+ function buildOutputPaths(vttFile, transcriptsDir) {
4025
+ const mdFile = toMdFilename(vttFile.filename);
4026
+ const relativeDir = dirname11(vttFile.relativePath);
4027
+ const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
4028
+ const outputPath = join16(outputDir, mdFile);
4029
+ return { outputDir, outputPath, mdFile, relativeDir };
4030
+ }
4031
+ function logSkipped(relativeDir, mdFile) {
4032
+ console.log(`Skipping (already exists): ${join16(relativeDir, mdFile)}`);
4033
+ return "skipped";
4034
+ }
4035
+ function ensureDirectory(dir, label) {
4036
+ if (!existsSync15(dir)) {
4037
+ mkdirSync4(dir, { recursive: true });
4038
+ console.log(`Created ${label}: ${dir}`);
4039
+ }
4040
+ }
4041
+ function processCues(content) {
3769
4042
  const cues = parseVtt(content);
3770
4043
  console.log(`Parsed ${cues.length} cues`);
3771
4044
  const dedupedCues = deduplicateCues(cues);
3772
4045
  console.log(`After deduplication: ${dedupedCues.length} cues`);
4046
+ return { cues, dedupedCues };
4047
+ }
4048
+ function convertToMessages(dedupedCues) {
3773
4049
  const chatMessages = cuesToChatMessages(dedupedCues);
3774
4050
  console.log(`Consolidated to ${chatMessages.length} chat messages`);
3775
- const output = formatChatLog(chatMessages);
3776
- writeFileSync12(outputPath, output, "utf-8");
4051
+ return chatMessages;
4052
+ }
4053
+ function logReduction(cueCount, messageCount) {
4054
+ console.log(`Reduction: ${cueCount} cues -> ${messageCount} messages
4055
+ `);
4056
+ }
4057
+ function readAndParseCues(inputPath) {
4058
+ console.log(`Reading: ${inputPath}`);
4059
+ return processCues(readFileSync12(inputPath, "utf-8"));
4060
+ }
4061
+ function writeFormatted(outputPath, content) {
4062
+ writeFileSync12(outputPath, content, "utf-8");
3777
4063
  console.log(`Written: ${outputPath}`);
4064
+ }
4065
+ function convertVttToMarkdown(inputPath, outputPath) {
4066
+ const { cues, dedupedCues } = readAndParseCues(inputPath);
4067
+ const chatMessages = convertToMessages(dedupedCues);
4068
+ writeFormatted(outputPath, formatChatLog(chatMessages));
4069
+ logReduction(cues.length, chatMessages.length);
4070
+ }
4071
+ function tryProcessVtt(vttFile, paths) {
4072
+ if (existsSync15(paths.outputPath))
4073
+ return logSkipped(paths.relativeDir, paths.mdFile);
4074
+ convertVttToMarkdown(vttFile.absolutePath, paths.outputPath);
4075
+ return "processed";
4076
+ }
4077
+ function processVttFile(vttFile, transcriptsDir) {
4078
+ const paths = buildOutputPaths(vttFile, transcriptsDir);
4079
+ ensureDirectory(paths.outputDir, "output directory");
4080
+ return tryProcessVtt(vttFile, paths);
4081
+ }
4082
+
4083
+ // src/commands/transcript/format/index.ts
4084
+ function logSummary(counts) {
3778
4085
  console.log(
3779
- `Reduction: ${cues.length} cues -> ${chatMessages.length} messages
3780
- `
4086
+ `
4087
+ Summary: ${counts.processed} processed, ${counts.skipped} skipped`
3781
4088
  );
3782
4089
  }
3783
- async function format() {
3784
- const { vttDir, transcriptsDir } = getTranscriptConfig();
3785
- if (!existsSync15(vttDir)) {
4090
+ function processAllFiles(vttFiles, transcriptsDir) {
4091
+ const counts = { processed: 0, skipped: 0 };
4092
+ for (const vttFile of vttFiles) {
4093
+ counts[processVttFile(vttFile, transcriptsDir)]++;
4094
+ }
4095
+ logSummary(counts);
4096
+ }
4097
+ function requireVttDir(vttDir) {
4098
+ if (!existsSync16(vttDir)) {
3786
4099
  console.error(`VTT directory not found: ${vttDir}`);
3787
4100
  process.exit(1);
3788
4101
  }
3789
- if (!existsSync15(transcriptsDir)) {
3790
- mkdirSync4(transcriptsDir, { recursive: true });
3791
- console.log(`Created output directory: ${transcriptsDir}`);
3792
- }
3793
- let vttFiles = findVttFilesRecursive(vttDir);
3794
- if (vttFiles.length === 0) {
3795
- console.log("No VTT files found in vtt directory.");
3796
- return;
3797
- }
3798
- console.log(`Found ${vttFiles.length} VTT file(s) in ${vttDir}
4102
+ }
4103
+ function logFoundFiles(files, vttDir) {
4104
+ console.log(`Found ${files.length} VTT file(s) in ${vttDir}
3799
4105
  `);
3800
- vttFiles = await fixInvalidDatePrefixes(vttFiles);
3801
- let processed = 0;
3802
- let skipped = 0;
3803
- for (const vttFile of vttFiles) {
3804
- let baseName = basename4(vttFile.filename, ".vtt");
3805
- baseName = baseName.replace(/\s*Transcription\s*/g, " ").trim();
3806
- const mdFile = `${baseName}.md`;
3807
- const relativeDir = dirname11(vttFile.relativePath);
3808
- const outputDir = relativeDir === "." ? transcriptsDir : join16(transcriptsDir, relativeDir);
3809
- const outputPath = join16(outputDir, mdFile);
3810
- if (!existsSync15(outputDir)) {
3811
- mkdirSync4(outputDir, { recursive: true });
3812
- console.log(`Created output directory: ${outputDir}`);
3813
- }
3814
- if (existsSync15(outputPath)) {
3815
- console.log(`Skipping (already exists): ${join16(relativeDir, mdFile)}`);
3816
- skipped++;
3817
- continue;
3818
- }
3819
- processFile(vttFile.absolutePath, outputPath);
3820
- processed++;
3821
- }
3822
- console.log(`
3823
- Summary: ${processed} processed, ${skipped} skipped`);
4106
+ }
4107
+ function logNoVttFiles() {
4108
+ console.log("No VTT files found in vtt directory.");
4109
+ return null;
4110
+ }
4111
+ function loadVttFiles(vttDir) {
4112
+ const files = findVttFilesRecursive(vttDir);
4113
+ if (files.length === 0) return logNoVttFiles();
4114
+ logFoundFiles(files, vttDir);
4115
+ return files;
4116
+ }
4117
+ async function formatLoadedFiles(vttFiles, transcriptsDir) {
4118
+ const fixed2 = await fixInvalidDatePrefixes(vttFiles);
4119
+ processAllFiles(fixed2, transcriptsDir);
4120
+ }
4121
+ async function format() {
4122
+ const { vttDir, transcriptsDir } = getTranscriptConfig();
4123
+ requireVttDir(vttDir);
4124
+ ensureDirectory(transcriptsDir, "output directory");
4125
+ const vttFiles = loadVttFiles(vttDir);
4126
+ if (vttFiles) await formatLoadedFiles(vttFiles, transcriptsDir);
3824
4127
  }
3825
4128
 
3826
4129
  // src/commands/transcript/summarise/index.ts
3827
- import { existsSync as existsSync17 } from "fs";
4130
+ import { existsSync as existsSync18 } from "fs";
3828
4131
  import { basename as basename5, dirname as dirname13, join as join18, relative as relative2 } from "path";
3829
4132
 
3830
4133
  // src/commands/transcript/summarise/processStagedFile/index.ts
3831
4134
  import {
3832
- existsSync as existsSync16,
4135
+ existsSync as existsSync17,
3833
4136
  mkdirSync as mkdirSync5,
3834
4137
  readFileSync as readFileSync13,
3835
4138
  renameSync as renameSync2,
@@ -3838,14 +4141,14 @@ import {
3838
4141
  import { dirname as dirname12, join as join17 } from "path";
3839
4142
 
3840
4143
  // src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
3841
- import chalk40 from "chalk";
4144
+ import chalk41 from "chalk";
3842
4145
  var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
3843
4146
  function validateStagedContent(filename, content) {
3844
4147
  const firstLine = content.split("\n")[0];
3845
4148
  const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
3846
4149
  if (!match) {
3847
4150
  console.error(
3848
- chalk40.red(
4151
+ chalk41.red(
3849
4152
  `Staged file ${filename} missing [Full Transcript](<path>) link on first line.`
3850
4153
  )
3851
4154
  );
@@ -3854,7 +4157,7 @@ function validateStagedContent(filename, content) {
3854
4157
  const contentAfterLink = content.slice(firstLine.length).trim();
3855
4158
  if (!contentAfterLink) {
3856
4159
  console.error(
3857
- chalk40.red(
4160
+ chalk41.red(
3858
4161
  `Staged file ${filename} has no summary content after the transcript link.`
3859
4162
  )
3860
4163
  );
@@ -3866,7 +4169,7 @@ function validateStagedContent(filename, content) {
3866
4169
  // src/commands/transcript/summarise/processStagedFile/index.ts
3867
4170
  var STAGING_DIR = join17(process.cwd(), ".assist", "transcript");
3868
4171
  function processStagedFile() {
3869
- if (!existsSync16(STAGING_DIR)) {
4172
+ if (!existsSync17(STAGING_DIR)) {
3870
4173
  return false;
3871
4174
  }
3872
4175
  const stagedFiles = findMdFilesRecursive(STAGING_DIR);
@@ -3890,7 +4193,7 @@ function processStagedFile() {
3890
4193
  }
3891
4194
  const destPath = join17(summaryDir, matchingTranscript.relativePath);
3892
4195
  const destDir = dirname12(destPath);
3893
- if (!existsSync16(destDir)) {
4196
+ if (!existsSync17(destDir)) {
3894
4197
  mkdirSync5(destDir, { recursive: true });
3895
4198
  }
3896
4199
  renameSync2(stagedFile.absolutePath, destPath);
@@ -3902,10 +4205,22 @@ function processStagedFile() {
3902
4205
  }
3903
4206
 
3904
4207
  // src/commands/transcript/summarise/index.ts
4208
+ function buildRelativeKey(relativePath, baseName) {
4209
+ const relDir = dirname13(relativePath);
4210
+ return relDir === "." ? baseName : join18(relDir, baseName);
4211
+ }
4212
+ function buildSummaryIndex(summaryDir) {
4213
+ const summaryFiles = findMdFilesRecursive(summaryDir);
4214
+ return new Set(
4215
+ summaryFiles.map(
4216
+ (f) => buildRelativeKey(f.relativePath, basename5(f.filename, ".md"))
4217
+ )
4218
+ );
4219
+ }
3905
4220
  function summarise() {
3906
4221
  processStagedFile();
3907
4222
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
3908
- if (!existsSync17(transcriptsDir)) {
4223
+ if (!existsSync18(transcriptsDir)) {
3909
4224
  console.log("No transcripts directory found.");
3910
4225
  return;
3911
4226
  }
@@ -3914,23 +4229,12 @@ function summarise() {
3914
4229
  console.log("No transcript files found.");
3915
4230
  return;
3916
4231
  }
3917
- const summaryFiles = findMdFilesRecursive(summaryDir);
3918
- const summaryRelativePaths = new Set(
3919
- summaryFiles.map((f) => {
3920
- const relDir = dirname13(f.relativePath);
3921
- const baseName = basename5(f.filename, ".md");
3922
- return relDir === "." ? baseName : join18(relDir, baseName);
3923
- })
4232
+ const summaryIndex = buildSummaryIndex(summaryDir);
4233
+ const missing = transcriptFiles.filter(
4234
+ (t) => !summaryIndex.has(
4235
+ buildRelativeKey(t.relativePath, getTranscriptBaseName(t.filename))
4236
+ )
3924
4237
  );
3925
- const missing = [];
3926
- for (const transcript of transcriptFiles) {
3927
- const transcriptBaseName = getTranscriptBaseName(transcript.filename);
3928
- const relDir = dirname13(transcript.relativePath);
3929
- const fullKey = relDir === "." ? transcriptBaseName : join18(relDir, transcriptBaseName);
3930
- if (!summaryRelativePaths.has(fullKey)) {
3931
- missing.push(transcript);
3932
- }
3933
- }
3934
4238
  if (missing.length === 0) {
3935
4239
  console.log("All transcripts have summaries.");
3936
4240
  return;
@@ -3997,9 +4301,9 @@ Total: ${lines.length} hardcoded color(s)`);
3997
4301
 
3998
4302
  // src/commands/verify/run/index.ts
3999
4303
  import { spawn as spawn3 } from "child_process";
4000
- import * as path24 from "path";
4304
+ import * as path25 from "path";
4001
4305
 
4002
- // src/commands/verify/run/printTaskStatuses.ts
4306
+ // src/commands/verify/run/createTimerCallback/printTaskStatuses.ts
4003
4307
  function formatDuration(ms) {
4004
4308
  if (ms < 1e3) {
4005
4309
  return `${ms}ms`;
@@ -4022,55 +4326,96 @@ function printTaskStatuses(tasks) {
4022
4326
  console.log("-------------------\n");
4023
4327
  }
4024
4328
 
4025
- // src/commands/verify/run/index.ts
4026
- async function run(options = {}) {
4027
- const { timer = false } = options;
4028
- const result = findPackageJsonWithVerifyScripts(process.cwd());
4029
- if (!result) {
4030
- console.log("No package.json with verify:* scripts found");
4031
- return;
4032
- }
4033
- const { packageJsonPath, verifyScripts } = result;
4034
- const packageDir = path24.dirname(packageJsonPath);
4035
- console.log(`Running ${verifyScripts.length} verify script(s) in parallel:`);
4036
- for (const script of verifyScripts) {
4037
- console.log(` - ${script}`);
4329
+ // src/commands/verify/run/createTimerCallback/index.ts
4330
+ function logFailedScripts(failed) {
4331
+ console.error(`
4332
+ ${failed.length} script(s) failed:`);
4333
+ for (const f of failed) {
4334
+ console.error(` - ${f.script} (exit code ${f.code})`);
4038
4335
  }
4039
- const taskStatuses = verifyScripts.map((script) => ({
4040
- script,
4041
- startTime: Date.now()
4042
- }));
4043
- const results = await Promise.all(
4336
+ }
4337
+ function createTimerCallback(taskStatuses, index) {
4338
+ return (exitCode) => {
4339
+ taskStatuses[index].endTime = Date.now();
4340
+ taskStatuses[index].code = exitCode;
4341
+ printTaskStatuses(taskStatuses);
4342
+ };
4343
+ }
4344
+ function initTaskStatuses(scripts) {
4345
+ return scripts.map((script) => ({ script, startTime: Date.now() }));
4346
+ }
4347
+
4348
+ // src/commands/verify/run/index.ts
4349
+ function spawnScript(script, cwd) {
4350
+ return spawn3("npm", ["run", script], { stdio: "inherit", shell: true, cwd });
4351
+ }
4352
+ function onScriptClose(script, onComplete, resolve) {
4353
+ return (code) => {
4354
+ const exitCode = code ?? 1;
4355
+ onComplete?.(exitCode);
4356
+ resolve({ script, code: exitCode });
4357
+ };
4358
+ }
4359
+ function runScript2(script, cwd, onComplete) {
4360
+ return new Promise((resolve) => {
4361
+ spawnScript(script, cwd).on(
4362
+ "close",
4363
+ onScriptClose(script, onComplete, resolve)
4364
+ );
4365
+ });
4366
+ }
4367
+ function runAllScripts(verifyScripts, packageDir, timer) {
4368
+ const taskStatuses = initTaskStatuses(verifyScripts);
4369
+ return Promise.all(
4044
4370
  verifyScripts.map(
4045
- (script, index) => new Promise((resolve) => {
4046
- const child = spawn3("npm", ["run", script], {
4047
- stdio: "inherit",
4048
- shell: true,
4049
- cwd: packageDir
4050
- });
4051
- child.on("close", (code) => {
4052
- const exitCode = code ?? 1;
4053
- if (timer) {
4054
- taskStatuses[index].endTime = Date.now();
4055
- taskStatuses[index].code = exitCode;
4056
- printTaskStatuses(taskStatuses);
4057
- }
4058
- resolve({ script, code: exitCode });
4059
- });
4060
- })
4371
+ (script, index) => runScript2(
4372
+ script,
4373
+ packageDir,
4374
+ timer ? createTimerCallback(taskStatuses, index) : void 0
4375
+ )
4061
4376
  )
4062
4377
  );
4063
- const failed = results.filter((r) => r.code !== 0);
4064
- if (failed.length > 0) {
4065
- console.error(`
4066
- ${failed.length} script(s) failed:`);
4067
- for (const f of failed) {
4068
- console.error(` - ${f.script} (exit code ${f.code})`);
4069
- }
4070
- process.exit(1);
4378
+ }
4379
+ function printScriptList(scripts) {
4380
+ console.log(`Running ${scripts.length} verify script(s) in parallel:`);
4381
+ for (const script of scripts) {
4382
+ console.log(` - ${script}`);
4071
4383
  }
4384
+ }
4385
+ function exitIfFailed(failed) {
4386
+ if (failed.length === 0) return;
4387
+ logFailedScripts(failed);
4388
+ process.exit(1);
4389
+ }
4390
+ function handleResults(results, totalCount) {
4391
+ exitIfFailed(results.filter((r) => r.code !== 0));
4072
4392
  console.log(`
4073
- All ${verifyScripts.length} verify script(s) passed`);
4393
+ All ${totalCount} verify script(s) passed`);
4394
+ }
4395
+ function resolveVerifyScripts() {
4396
+ const result = findPackageJsonWithVerifyScripts(process.cwd());
4397
+ if (!result) {
4398
+ console.log("No package.json with verify:* scripts found");
4399
+ return null;
4400
+ }
4401
+ return result;
4402
+ }
4403
+ function getPackageDir(found) {
4404
+ return path25.dirname(found.packageJsonPath);
4405
+ }
4406
+ async function executeVerifyScripts(found, timer) {
4407
+ printScriptList(found.verifyScripts);
4408
+ const results = await runAllScripts(
4409
+ found.verifyScripts,
4410
+ getPackageDir(found),
4411
+ timer
4412
+ );
4413
+ handleResults(results, found.verifyScripts.length);
4414
+ }
4415
+ async function run(options = {}) {
4416
+ const found = resolveVerifyScripts();
4417
+ if (!found) return;
4418
+ await executeVerifyScripts(found, options.timer ?? false);
4074
4419
  }
4075
4420
 
4076
4421
  // src/commands/registerVerify.ts
@@ -4080,70 +4425,114 @@ function registerVerify(program2) {
4080
4425
  verifyCommand.command("hardcoded-colors").description("Check for hardcoded hex colors in src/").action(hardcodedColors);
4081
4426
  }
4082
4427
 
4083
- // src/commands/run.ts
4428
+ // src/commands/run/index.ts
4084
4429
  import { spawn as spawn4 } from "child_process";
4085
- function run2(name, args) {
4086
- const config = loadConfig();
4087
- if (!config.run || config.run.length === 0) {
4088
- console.error("No run configurations found in assist.yml");
4089
- process.exit(1);
4090
- }
4091
- const runConfig = config.run.find((r) => r.name === name);
4092
- if (!runConfig) {
4093
- console.error(`No run configuration found with name: ${name}`);
4094
- console.error("Available configurations:");
4095
- for (const r of config.run) {
4096
- console.error(` - ${r.name}`);
4097
- }
4430
+
4431
+ // src/commands/run/add.ts
4432
+ function findAddIndex() {
4433
+ const addIndex = process.argv.indexOf("add");
4434
+ if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
4435
+ return addIndex;
4436
+ }
4437
+ function extractAddArgs(addIndex) {
4438
+ return {
4439
+ name: process.argv[addIndex + 1],
4440
+ command: process.argv[addIndex + 2],
4441
+ args: process.argv.slice(addIndex + 3)
4442
+ };
4443
+ }
4444
+ function parseAddArguments() {
4445
+ const addIndex = findAddIndex();
4446
+ return addIndex === -1 ? null : extractAddArgs(addIndex);
4447
+ }
4448
+ function buildRunEntry(name, command, args) {
4449
+ const entry = {
4450
+ name,
4451
+ command
4452
+ };
4453
+ if (args.length > 0) entry.args = args;
4454
+ return entry;
4455
+ }
4456
+ function ensureNoDuplicate(configs, name) {
4457
+ if (configs.find((r) => r.name === name)) {
4458
+ console.error(`Run configuration with name "${name}" already exists`);
4098
4459
  process.exit(1);
4099
4460
  }
4100
- const command = runConfig.command.includes(" ") ? `"${runConfig.command}"` : runConfig.command;
4101
- const allArgs = [...runConfig.args ?? [], ...args];
4102
- const quotedArgs = allArgs.map(
4103
- (arg) => arg.includes(" ") ? `"${arg}"` : arg
4104
- );
4105
- const fullCommand = [command, ...quotedArgs].join(" ");
4106
- const child = spawn4(fullCommand, [], {
4107
- stdio: "inherit",
4108
- shell: true
4109
- });
4110
- child.on("close", (code) => {
4111
- process.exit(code ?? 0);
4112
- });
4113
- child.on("error", (err) => {
4114
- console.error(`Failed to execute command: ${err.message}`);
4115
- process.exit(1);
4116
- });
4117
4461
  }
4118
- function add() {
4119
- const addIndex = process.argv.indexOf("add");
4120
- if (addIndex === -1 || addIndex + 2 >= process.argv.length) {
4462
+ function formatDisplay(command, args) {
4463
+ return args.length > 0 ? `${command} ${args.join(" ")}` : command;
4464
+ }
4465
+ function requireParsedArgs() {
4466
+ const parsed = parseAddArguments();
4467
+ if (!parsed) {
4121
4468
  console.error("Usage: assist run add <name> <command> [args...]");
4122
4469
  process.exit(1);
4123
4470
  }
4124
- const name = process.argv[addIndex + 1];
4125
- const command = process.argv[addIndex + 2];
4126
- const args = process.argv.slice(addIndex + 3);
4471
+ return parsed;
4472
+ }
4473
+ function getOrInitRunList() {
4127
4474
  const config = loadConfig();
4128
- if (!config.run) {
4129
- config.run = [];
4130
- }
4131
- const existing = config.run.find((r) => r.name === name);
4132
- if (existing) {
4133
- console.error(`Run configuration with name "${name}" already exists`);
4134
- process.exit(1);
4135
- }
4136
- const entry = {
4137
- name,
4138
- command
4139
- };
4140
- if (args.length > 0) {
4141
- entry.args = args;
4142
- }
4143
- config.run.push(entry);
4475
+ if (!config.run) config.run = [];
4476
+ return { config, runList: config.run };
4477
+ }
4478
+ function saveNewRunConfig(name, command, args) {
4479
+ const { config, runList } = getOrInitRunList();
4480
+ ensureNoDuplicate(runList, name);
4481
+ runList.push(buildRunEntry(name, command, args));
4144
4482
  saveConfig(config);
4145
- const display = args.length > 0 ? `${command} ${args.join(" ")}` : command;
4146
- console.log(`Added run configuration: ${name} -> ${display}`);
4483
+ }
4484
+ function add() {
4485
+ const { name, command, args } = requireParsedArgs();
4486
+ saveNewRunConfig(name, command, args);
4487
+ console.log(
4488
+ `Added run configuration: ${name} -> ${formatDisplay(command, args)}`
4489
+ );
4490
+ }
4491
+
4492
+ // src/commands/run/index.ts
4493
+ function quoteIfNeeded(arg) {
4494
+ return arg.includes(" ") ? `"${arg}"` : arg;
4495
+ }
4496
+ function buildCommand(command, configArgs, extraArgs) {
4497
+ const allArgs = [...configArgs, ...extraArgs];
4498
+ return [quoteIfNeeded(command), ...allArgs.map(quoteIfNeeded)].join(" ");
4499
+ }
4500
+ function printAvailableConfigs(configs) {
4501
+ console.error("Available configurations:");
4502
+ for (const r of configs) {
4503
+ console.error(` - ${r.name}`);
4504
+ }
4505
+ }
4506
+ function exitNoRunConfigs() {
4507
+ console.error("No run configurations found in assist.yml");
4508
+ process.exit(1);
4509
+ }
4510
+ function requireRunConfigs() {
4511
+ const { run: run3 } = loadConfig();
4512
+ if (!run3 || run3.length === 0) return exitNoRunConfigs();
4513
+ return run3;
4514
+ }
4515
+ function exitWithConfigNotFound(name, configs) {
4516
+ console.error(`No run configuration found with name: ${name}`);
4517
+ printAvailableConfigs(configs);
4518
+ process.exit(1);
4519
+ }
4520
+ function findRunConfig(name) {
4521
+ const configs = requireRunConfigs();
4522
+ return configs.find((r) => r.name === name) ?? exitWithConfigNotFound(name, configs);
4523
+ }
4524
+ function onSpawnError(err) {
4525
+ console.error(`Failed to execute command: ${err.message}`);
4526
+ process.exit(1);
4527
+ }
4528
+ function spawnCommand(fullCommand) {
4529
+ const child = spawn4(fullCommand, [], { stdio: "inherit", shell: true });
4530
+ child.on("close", (code) => process.exit(code ?? 0));
4531
+ child.on("error", onSpawnError);
4532
+ }
4533
+ function run2(name, args) {
4534
+ const runConfig = findRunConfig(name);
4535
+ spawnCommand(buildCommand(runConfig.command, runConfig.args ?? [], args));
4147
4536
  }
4148
4537
 
4149
4538
  // src/commands/statusLine.ts
@@ -4165,29 +4554,29 @@ async function statusLine() {
4165
4554
  }
4166
4555
 
4167
4556
  // src/commands/sync.ts
4168
- import * as fs22 from "fs";
4557
+ import * as fs23 from "fs";
4169
4558
  import * as os from "os";
4170
- import * as path27 from "path";
4559
+ import * as path28 from "path";
4171
4560
  import { fileURLToPath as fileURLToPath3 } from "url";
4172
4561
 
4173
4562
  // src/commands/sync/syncClaudeMd.ts
4174
- import * as fs20 from "fs";
4175
- import * as path25 from "path";
4176
- import chalk41 from "chalk";
4563
+ import * as fs21 from "fs";
4564
+ import * as path26 from "path";
4565
+ import chalk42 from "chalk";
4177
4566
  async function syncClaudeMd(claudeDir, targetBase) {
4178
- const source = path25.join(claudeDir, "CLAUDE.md");
4179
- const target = path25.join(targetBase, "CLAUDE.md");
4180
- const sourceContent = fs20.readFileSync(source, "utf-8");
4181
- if (fs20.existsSync(target)) {
4182
- const targetContent = fs20.readFileSync(target, "utf-8");
4567
+ const source = path26.join(claudeDir, "CLAUDE.md");
4568
+ const target = path26.join(targetBase, "CLAUDE.md");
4569
+ const sourceContent = fs21.readFileSync(source, "utf-8");
4570
+ if (fs21.existsSync(target)) {
4571
+ const targetContent = fs21.readFileSync(target, "utf-8");
4183
4572
  if (sourceContent !== targetContent) {
4184
4573
  console.log(
4185
- chalk41.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
4574
+ chalk42.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
4186
4575
  );
4187
4576
  console.log();
4188
4577
  printDiff(targetContent, sourceContent);
4189
4578
  const confirm = await promptConfirm(
4190
- chalk41.red("Overwrite existing CLAUDE.md?"),
4579
+ chalk42.red("Overwrite existing CLAUDE.md?"),
4191
4580
  false
4192
4581
  );
4193
4582
  if (!confirm) {
@@ -4196,30 +4585,30 @@ async function syncClaudeMd(claudeDir, targetBase) {
4196
4585
  }
4197
4586
  }
4198
4587
  }
4199
- fs20.copyFileSync(source, target);
4588
+ fs21.copyFileSync(source, target);
4200
4589
  console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
4201
4590
  }
4202
4591
 
4203
4592
  // src/commands/sync/syncSettings.ts
4204
- import * as fs21 from "fs";
4205
- import * as path26 from "path";
4206
- import chalk42 from "chalk";
4593
+ import * as fs22 from "fs";
4594
+ import * as path27 from "path";
4595
+ import chalk43 from "chalk";
4207
4596
  async function syncSettings(claudeDir, targetBase) {
4208
- const source = path26.join(claudeDir, "settings.json");
4209
- const target = path26.join(targetBase, "settings.json");
4210
- const sourceContent = fs21.readFileSync(source, "utf-8");
4597
+ const source = path27.join(claudeDir, "settings.json");
4598
+ const target = path27.join(targetBase, "settings.json");
4599
+ const sourceContent = fs22.readFileSync(source, "utf-8");
4211
4600
  const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
4212
- if (fs21.existsSync(target)) {
4213
- const targetContent = fs21.readFileSync(target, "utf-8");
4601
+ if (fs22.existsSync(target)) {
4602
+ const targetContent = fs22.readFileSync(target, "utf-8");
4214
4603
  const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
4215
4604
  if (normalizedSource !== normalizedTarget) {
4216
4605
  console.log(
4217
- chalk42.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
4606
+ chalk43.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
4218
4607
  );
4219
4608
  console.log();
4220
4609
  printDiff(targetContent, sourceContent);
4221
4610
  const confirm = await promptConfirm(
4222
- chalk42.red("Overwrite existing settings.json?"),
4611
+ chalk43.red("Overwrite existing settings.json?"),
4223
4612
  false
4224
4613
  );
4225
4614
  if (!confirm) {
@@ -4228,27 +4617,27 @@ async function syncSettings(claudeDir, targetBase) {
4228
4617
  }
4229
4618
  }
4230
4619
  }
4231
- fs21.copyFileSync(source, target);
4620
+ fs22.copyFileSync(source, target);
4232
4621
  console.log("Copied settings.json to ~/.claude/settings.json");
4233
4622
  }
4234
4623
 
4235
4624
  // src/commands/sync.ts
4236
4625
  var __filename2 = fileURLToPath3(import.meta.url);
4237
- var __dirname4 = path27.dirname(__filename2);
4626
+ var __dirname4 = path28.dirname(__filename2);
4238
4627
  async function sync() {
4239
- const claudeDir = path27.join(__dirname4, "..", "claude");
4240
- const targetBase = path27.join(os.homedir(), ".claude");
4628
+ const claudeDir = path28.join(__dirname4, "..", "claude");
4629
+ const targetBase = path28.join(os.homedir(), ".claude");
4241
4630
  syncCommands(claudeDir, targetBase);
4242
4631
  await syncSettings(claudeDir, targetBase);
4243
4632
  await syncClaudeMd(claudeDir, targetBase);
4244
4633
  }
4245
4634
  function syncCommands(claudeDir, targetBase) {
4246
- const sourceDir = path27.join(claudeDir, "commands");
4247
- const targetDir = path27.join(targetBase, "commands");
4248
- fs22.mkdirSync(targetDir, { recursive: true });
4249
- const files = fs22.readdirSync(sourceDir);
4635
+ const sourceDir = path28.join(claudeDir, "commands");
4636
+ const targetDir = path28.join(targetBase, "commands");
4637
+ fs23.mkdirSync(targetDir, { recursive: true });
4638
+ const files = fs23.readdirSync(sourceDir);
4250
4639
  for (const file of files) {
4251
- fs22.copyFileSync(path27.join(sourceDir, file), path27.join(targetDir, file));
4640
+ fs23.copyFileSync(path28.join(sourceDir, file), path28.join(targetDir, file));
4252
4641
  console.log(`Copied ${file} to ${targetDir}`);
4253
4642
  }
4254
4643
  console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);