@staff0rd/assist 0.42.2 → 0.43.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
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { execSync as execSync18 } from "child_process";
4
+ import { execSync as execSync19 } from "child_process";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@staff0rd/assist",
10
- version: "0.42.2",
10
+ version: "0.43.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 55",
31
+ "verify:maintainability": "assist complexity maintainability ./src --threshold 60",
32
32
  "verify:custom-lint": "assist lint"
33
33
  },
34
34
  keywords: [],
@@ -107,6 +107,9 @@ var assistConfigSchema = z.strictObject({
107
107
  complexity: z.strictObject({
108
108
  ignore: z.array(z.string()).default(["**/*test.ts*"])
109
109
  }).default({ ignore: ["**/*test.ts*"] }),
110
+ restructure: z.strictObject({
111
+ ignore: z.array(z.string()).default([])
112
+ }).optional(),
110
113
  run: z.array(runConfigSchema).optional(),
111
114
  transcript: transcriptConfigSchema.optional()
112
115
  });
@@ -549,57 +552,67 @@ function calculateMaintainabilityIndex(halsteadVolume, cyclomaticComplexity, slo
549
552
  const mi = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(sloc2);
550
553
  return Math.max(0, Math.min(100, mi));
551
554
  }
552
- async function maintainability(pattern2 = "**/*.ts", options = {}) {
553
- withSourceFiles(pattern2, (files) => {
554
- const fileMetrics = /* @__PURE__ */ new Map();
555
- for (const file of files) {
556
- const content = fs3.readFileSync(file, "utf-8");
557
- fileMetrics.set(file, { sloc: countSloc(content), functions: [] });
558
- }
559
- forEachFunction(files, (file, _name, node) => {
560
- const metrics = fileMetrics.get(file);
561
- if (metrics) {
562
- const complexity = calculateCyclomaticComplexity(node);
563
- const halstead2 = calculateHalstead(node);
564
- const mi = calculateMaintainabilityIndex(
565
- halstead2.volume,
566
- complexity,
567
- metrics.sloc
568
- );
569
- metrics.functions.push(mi);
570
- }
571
- });
572
- const results = [];
573
- const { threshold } = options;
574
- for (const [file, metrics] of fileMetrics) {
575
- if (metrics.functions.length === 0) continue;
576
- const avgMaintainability = metrics.functions.reduce((a, b) => a + b, 0) / metrics.functions.length;
577
- const minMaintainability = Math.min(...metrics.functions);
578
- results.push({ file, avgMaintainability, minMaintainability });
579
- }
580
- results.sort((a, b) => a.minMaintainability - b.minMaintainability);
581
- const filtered = threshold !== void 0 ? results.filter((r) => r.minMaintainability < threshold) : results;
582
- if (threshold !== void 0 && filtered.length === 0) {
583
- console.log(chalk5.green("All files pass maintainability threshold"));
584
- } else {
585
- for (const { file, avgMaintainability, minMaintainability } of filtered) {
586
- const color = threshold !== void 0 ? chalk5.red : chalk5.white;
587
- console.log(
588
- `${color(file)} \u2192 avg: ${chalk5.cyan(avgMaintainability.toFixed(1))}, min: ${chalk5.yellow(minMaintainability.toFixed(1))}`
589
- );
590
- }
555
+ function collectFileMetrics(files) {
556
+ const fileMetrics = /* @__PURE__ */ new Map();
557
+ for (const file of files) {
558
+ const content = fs3.readFileSync(file, "utf-8");
559
+ fileMetrics.set(file, { sloc: countSloc(content), functions: [] });
560
+ }
561
+ forEachFunction(files, (file, _name, node) => {
562
+ const metrics = fileMetrics.get(file);
563
+ if (metrics) {
564
+ const complexity = calculateCyclomaticComplexity(node);
565
+ const halstead2 = calculateHalstead(node);
566
+ const mi = calculateMaintainabilityIndex(
567
+ halstead2.volume,
568
+ complexity,
569
+ metrics.sloc
570
+ );
571
+ metrics.functions.push(mi);
591
572
  }
592
- console.log(chalk5.dim(`
593
- Analyzed ${results.length} files`));
594
- if (filtered.length > 0 && threshold !== void 0) {
595
- console.error(
596
- chalk5.red(
597
- `
598
- Fail: ${filtered.length} file(s) below threshold ${threshold}. Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code. Try 'complexity cyclomatic', 'complexity halstead', or 'complexity sloc' to help identify which metric is contributing most. For larger files, start by extracting responsibilities into smaller files.`
599
- )
573
+ });
574
+ return fileMetrics;
575
+ }
576
+ function aggregateResults(fileMetrics) {
577
+ const results = [];
578
+ for (const [file, metrics] of fileMetrics) {
579
+ if (metrics.functions.length === 0) continue;
580
+ const avgMaintainability = metrics.functions.reduce((a, b) => a + b, 0) / metrics.functions.length;
581
+ const minMaintainability = Math.min(...metrics.functions);
582
+ results.push({ file, avgMaintainability, minMaintainability });
583
+ }
584
+ results.sort((a, b) => a.minMaintainability - b.minMaintainability);
585
+ return results;
586
+ }
587
+ function displayResults(results, threshold) {
588
+ const filtered = threshold !== void 0 ? results.filter((r) => r.minMaintainability < threshold) : results;
589
+ if (threshold !== void 0 && filtered.length === 0) {
590
+ console.log(chalk5.green("All files pass maintainability threshold"));
591
+ } else {
592
+ for (const { file, avgMaintainability, minMaintainability } of filtered) {
593
+ const color = threshold !== void 0 ? chalk5.red : chalk5.white;
594
+ console.log(
595
+ `${color(file)} \u2192 avg: ${chalk5.cyan(avgMaintainability.toFixed(1))}, min: ${chalk5.yellow(minMaintainability.toFixed(1))}`
600
596
  );
601
- process.exit(1);
602
597
  }
598
+ }
599
+ console.log(chalk5.dim(`
600
+ Analyzed ${results.length} files`));
601
+ if (filtered.length > 0 && threshold !== void 0) {
602
+ console.error(
603
+ chalk5.red(
604
+ `
605
+ Fail: ${filtered.length} file(s) below threshold ${threshold}. Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code. Try 'complexity cyclomatic', 'complexity halstead', or 'complexity sloc' to help identify which metric is contributing most. For larger files, start by extracting responsibilities into smaller files.`
606
+ )
607
+ );
608
+ process.exit(1);
609
+ }
610
+ }
611
+ async function maintainability(pattern2 = "**/*.ts", options = {}) {
612
+ withSourceFiles(pattern2, (files) => {
613
+ const fileMetrics = collectFileMetrics(files);
614
+ const results = aggregateResults(fileMetrics);
615
+ displayResults(results, options.threshold);
603
616
  });
604
617
  }
605
618
 
@@ -638,8 +651,8 @@ Total: ${total} lines across ${files.length} files`)
638
651
  // src/commands/config/index.ts
639
652
  import chalk7 from "chalk";
640
653
  import { stringify as stringifyYaml2 } from "yaml";
641
- function getNestedValue(obj, path20) {
642
- const keys = path20.split(".");
654
+ function getNestedValue(obj, path28) {
655
+ const keys = path28.split(".");
643
656
  let current = obj;
644
657
  for (const key of keys) {
645
658
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -649,8 +662,8 @@ function getNestedValue(obj, path20) {
649
662
  }
650
663
  return current;
651
664
  }
652
- function setNestedValue(obj, path20, value) {
653
- const keys = path20.split(".");
665
+ function setNestedValue(obj, path28, value) {
666
+ const keys = path28.split(".");
654
667
  const result = { ...obj };
655
668
  let current = result;
656
669
  for (let i = 0; i < keys.length - 1; i++) {
@@ -677,8 +690,8 @@ function configSet(key, value) {
677
690
  const result = assistConfigSchema.safeParse(updated);
678
691
  if (!result.success) {
679
692
  for (const issue of result.error.issues) {
680
- const path20 = issue.path.length > 0 ? issue.path.join(".") : key;
681
- console.error(chalk7.red(`${path20}: ${issue.message}`));
693
+ const path28 = issue.path.length > 0 ? issue.path.join(".") : key;
694
+ console.error(chalk7.red(`${path28}: ${issue.message}`));
682
695
  }
683
696
  process.exit(1);
684
697
  }
@@ -705,10 +718,7 @@ function configList() {
705
718
 
706
719
  // src/commands/deploy/init.ts
707
720
  import { execSync as execSync2 } from "child_process";
708
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
709
- import { dirname, join as join2 } from "path";
710
- import { fileURLToPath } from "url";
711
- import chalk9 from "chalk";
721
+ import chalk10 from "chalk";
712
722
  import enquirer2 from "enquirer";
713
723
 
714
724
  // src/shared/promptConfirm.ts
@@ -728,6 +738,12 @@ async function promptConfirm(message, initial = true) {
728
738
  return confirmed;
729
739
  }
730
740
 
741
+ // src/commands/deploy/updateWorkflow.ts
742
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
743
+ import { dirname, join as join2 } from "path";
744
+ import { fileURLToPath } from "url";
745
+ import chalk9 from "chalk";
746
+
731
747
  // src/utils/printDiff.ts
732
748
  import chalk8 from "chalk";
733
749
  import * as diff from "diff";
@@ -757,7 +773,7 @@ function printDiff(oldContent, newContent) {
757
773
  }
758
774
  }
759
775
 
760
- // src/commands/deploy/init.ts
776
+ // src/commands/deploy/updateWorkflow.ts
761
777
  var WORKFLOW_PATH = ".github/workflows/build.yml";
762
778
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
763
779
  function getExistingSiteId() {
@@ -798,11 +814,13 @@ async function updateWorkflow(siteId) {
798
814
  console.log(chalk9.green(`
799
815
  Created ${WORKFLOW_PATH}`));
800
816
  }
817
+
818
+ // src/commands/deploy/init.ts
801
819
  async function init() {
802
- console.log(chalk9.bold("Initializing Netlify deployment...\n"));
820
+ console.log(chalk10.bold("Initializing Netlify deployment...\n"));
803
821
  const existingSiteId = getExistingSiteId();
804
822
  if (existingSiteId) {
805
- console.log(chalk9.dim(`Using existing site ID: ${existingSiteId}
823
+ console.log(chalk10.dim(`Using existing site ID: ${existingSiteId}
806
824
  `));
807
825
  await updateWorkflow(existingSiteId);
808
826
  return;
@@ -814,10 +832,10 @@ async function init() {
814
832
  });
815
833
  } catch (error) {
816
834
  if (error instanceof Error && error.message.includes("command not found")) {
817
- console.error(chalk9.red("\nNetlify CLI is not installed.\n"));
835
+ console.error(chalk10.red("\nNetlify CLI is not installed.\n"));
818
836
  const install = await promptConfirm("Would you like to install it now?");
819
837
  if (install) {
820
- console.log(chalk9.dim("\nInstalling netlify-cli...\n"));
838
+ console.log(chalk10.dim("\nInstalling netlify-cli...\n"));
821
839
  execSync2("npm install -g netlify-cli", { stdio: "inherit" });
822
840
  console.log();
823
841
  execSync2("netlify sites:create --disable-linking", {
@@ -825,7 +843,7 @@ async function init() {
825
843
  });
826
844
  } else {
827
845
  console.log(
828
- chalk9.yellow(
846
+ chalk10.yellow(
829
847
  "\nInstall it manually with: npm install -g netlify-cli\n"
830
848
  )
831
849
  );
@@ -842,17 +860,17 @@ async function init() {
842
860
  validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
843
861
  });
844
862
  await updateWorkflow(siteId);
845
- console.log(chalk9.bold("\nDeployment initialized successfully!"));
863
+ console.log(chalk10.bold("\nDeployment initialized successfully!"));
846
864
  console.log(
847
- chalk9.yellow("\nTo complete setup, create a personal access token at:")
865
+ chalk10.yellow("\nTo complete setup, create a personal access token at:")
848
866
  );
849
867
  console.log(
850
- chalk9.cyan(
868
+ chalk10.cyan(
851
869
  "https://app.netlify.com/user/applications#personal-access-tokens"
852
870
  )
853
871
  );
854
872
  console.log(
855
- chalk9.yellow(
873
+ chalk10.yellow(
856
874
  "\nThen add it as NETLIFY_AUTH_TOKEN in your GitHub repository secrets."
857
875
  )
858
876
  );
@@ -860,7 +878,7 @@ async function init() {
860
878
 
861
879
  // src/commands/deploy/redirect.ts
862
880
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
863
- import chalk10 from "chalk";
881
+ import chalk11 from "chalk";
864
882
  var TRAILING_SLASH_SCRIPT = ` <script>
865
883
  if (!window.location.pathname.endsWith('/')) {
866
884
  window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
@@ -869,32 +887,32 @@ var TRAILING_SLASH_SCRIPT = ` <script>
869
887
  function redirect() {
870
888
  const indexPath = "index.html";
871
889
  if (!existsSync3(indexPath)) {
872
- console.log(chalk10.yellow("No index.html found"));
890
+ console.log(chalk11.yellow("No index.html found"));
873
891
  return;
874
892
  }
875
893
  const content = readFileSync3(indexPath, "utf-8");
876
894
  if (content.includes("window.location.pathname.endsWith('/')")) {
877
- console.log(chalk10.dim("Trailing slash script already present"));
895
+ console.log(chalk11.dim("Trailing slash script already present"));
878
896
  return;
879
897
  }
880
898
  const headCloseIndex = content.indexOf("</head>");
881
899
  if (headCloseIndex === -1) {
882
- console.log(chalk10.red("Could not find </head> tag in index.html"));
900
+ console.log(chalk11.red("Could not find </head> tag in index.html"));
883
901
  return;
884
902
  }
885
903
  const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
886
904
  writeFileSync3(indexPath, newContent);
887
- console.log(chalk10.green("Added trailing slash redirect to index.html"));
905
+ console.log(chalk11.green("Added trailing slash redirect to index.html"));
888
906
  }
889
907
 
890
908
  // src/commands/devlog/list.ts
891
909
  import { execSync as execSync4 } from "child_process";
892
910
  import { basename as basename2 } from "path";
893
- import chalk12 from "chalk";
911
+ import chalk13 from "chalk";
894
912
 
895
913
  // src/commands/devlog/shared.ts
896
914
  import { execSync as execSync3 } from "child_process";
897
- import chalk11 from "chalk";
915
+ import chalk12 from "chalk";
898
916
 
899
917
  // src/commands/devlog/loadDevlogEntries.ts
900
918
  import { readdirSync, readFileSync as readFileSync4 } from "fs";
@@ -955,17 +973,35 @@ function shouldIgnoreCommit(files, ignorePaths) {
955
973
  }
956
974
  function printCommitsWithFiles(commits, ignore2, verbose) {
957
975
  for (const commit2 of commits) {
958
- console.log(` ${chalk11.yellow(commit2.hash)} ${commit2.message}`);
976
+ console.log(` ${chalk12.yellow(commit2.hash)} ${commit2.message}`);
959
977
  if (verbose) {
960
978
  const visibleFiles = commit2.files.filter(
961
979
  (file) => !ignore2.some((p) => file.startsWith(p))
962
980
  );
963
981
  for (const file of visibleFiles) {
964
- console.log(` ${chalk11.dim(file)}`);
982
+ console.log(` ${chalk12.dim(file)}`);
965
983
  }
966
984
  }
967
985
  }
968
986
  }
987
+ function parseGitLogCommits(output, ignore2, afterDate) {
988
+ const lines = output.trim().split("\n");
989
+ const commitsByDate = /* @__PURE__ */ new Map();
990
+ for (const line of lines) {
991
+ const [date, hash, ...messageParts] = line.split("|");
992
+ const message = messageParts.join("|");
993
+ if (afterDate && date <= afterDate) {
994
+ continue;
995
+ }
996
+ const files = getCommitFiles(hash);
997
+ if (!shouldIgnoreCommit(files, ignore2)) {
998
+ const existing = commitsByDate.get(date) || [];
999
+ existing.push({ date, hash, message, files });
1000
+ commitsByDate.set(date, existing);
1001
+ }
1002
+ }
1003
+ return commitsByDate;
1004
+ }
969
1005
 
970
1006
  // src/commands/devlog/list.ts
971
1007
  function list(options) {
@@ -981,22 +1017,7 @@ function list(options) {
981
1017
  `git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
982
1018
  { encoding: "utf-8" }
983
1019
  );
984
- const lines = output.trim().split("\n");
985
- const commits = [];
986
- for (const line of lines) {
987
- const [date, hash, ...messageParts] = line.split("|");
988
- const message = messageParts.join("|");
989
- const files = getCommitFiles(hash);
990
- if (!shouldIgnoreCommit(files, ignore2)) {
991
- commits.push({ date, hash, message, files });
992
- }
993
- }
994
- const commitsByDate = /* @__PURE__ */ new Map();
995
- for (const commit2 of commits) {
996
- const existing = commitsByDate.get(commit2.date) || [];
997
- existing.push(commit2);
998
- commitsByDate.set(commit2.date, existing);
999
- }
1020
+ const commitsByDate = parseGitLogCommits(output, ignore2);
1000
1021
  let dateCount = 0;
1001
1022
  let isFirst = true;
1002
1023
  for (const [date, dateCommits] of commitsByDate) {
@@ -1014,12 +1035,12 @@ function list(options) {
1014
1035
  isFirst = false;
1015
1036
  const entries = devlogEntries.get(date);
1016
1037
  if (skipDays.has(date)) {
1017
- console.log(`${chalk12.bold.blue(date)} ${chalk12.dim("skipped")}`);
1038
+ console.log(`${chalk13.bold.blue(date)} ${chalk13.dim("skipped")}`);
1018
1039
  } else if (entries && entries.length > 0) {
1019
- const entryInfo = entries.map((e) => `${chalk12.green(e.version)} ${e.title}`).join(" | ");
1020
- console.log(`${chalk12.bold.blue(date)} ${entryInfo}`);
1040
+ const entryInfo = entries.map((e) => `${chalk13.green(e.version)} ${e.title}`).join(" | ");
1041
+ console.log(`${chalk13.bold.blue(date)} ${entryInfo}`);
1021
1042
  } else {
1022
- console.log(`${chalk12.bold.blue(date)} ${chalk12.red("\u26A0 devlog missing")}`);
1043
+ console.log(`${chalk13.bold.blue(date)} ${chalk13.red("\u26A0 devlog missing")}`);
1023
1044
  }
1024
1045
  printCommitsWithFiles(dateCommits, ignore2, options.verbose ?? false);
1025
1046
  }
@@ -1027,7 +1048,7 @@ function list(options) {
1027
1048
 
1028
1049
  // src/commands/devlog/next.ts
1029
1050
  import { execSync as execSync6 } from "child_process";
1030
- import chalk13 from "chalk";
1051
+ import chalk14 from "chalk";
1031
1052
 
1032
1053
  // src/commands/devlog/getLastVersionInfo.ts
1033
1054
  import { execSync as execSync5 } from "child_process";
@@ -1104,6 +1125,22 @@ function bumpVersion(version2, type) {
1104
1125
  }
1105
1126
 
1106
1127
  // src/commands/devlog/next.ts
1128
+ function displayVersion(conventional, firstHash, patchVersion, minorVersion) {
1129
+ if (conventional && firstHash) {
1130
+ const version2 = getVersionAtCommit(firstHash);
1131
+ if (version2) {
1132
+ console.log(`${chalk14.bold("version:")} ${stripToMinor(version2)}`);
1133
+ } else {
1134
+ console.log(`${chalk14.bold("version:")} ${chalk14.red("unknown")}`);
1135
+ }
1136
+ } else if (patchVersion && minorVersion) {
1137
+ console.log(
1138
+ `${chalk14.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
1139
+ );
1140
+ } else {
1141
+ console.log(`${chalk14.bold("version:")} v0.1 (initial)`);
1142
+ }
1143
+ }
1107
1144
  function next(options) {
1108
1145
  const config = loadConfig();
1109
1146
  const ignore2 = options.ignore ?? config.devlog?.ignore ?? [];
@@ -1117,62 +1154,40 @@ function next(options) {
1117
1154
  "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
1118
1155
  { encoding: "utf-8" }
1119
1156
  );
1120
- const lines = output.trim().split("\n");
1121
- const commitsByDate = /* @__PURE__ */ new Map();
1122
- for (const line of lines) {
1123
- const [date, hash, ...messageParts] = line.split("|");
1124
- const message = messageParts.join("|");
1125
- if (lastDate && date <= lastDate) {
1126
- continue;
1127
- }
1128
- const files = getCommitFiles(hash);
1129
- if (!shouldIgnoreCommit(files, ignore2)) {
1130
- const existing = commitsByDate.get(date) || [];
1131
- existing.push({ date, hash, message, files });
1132
- commitsByDate.set(date, existing);
1133
- }
1134
- }
1157
+ const commitsByDate = parseGitLogCommits(output, ignore2, lastDate);
1135
1158
  const dates = Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort();
1136
1159
  const targetDate = dates[0];
1137
1160
  if (!targetDate) {
1138
1161
  if (lastInfo) {
1139
- console.log(chalk13.dim("No commits after last versioned entry"));
1162
+ console.log(chalk14.dim("No commits after last versioned entry"));
1140
1163
  } else {
1141
- console.log(chalk13.dim("No commits found"));
1164
+ console.log(chalk14.dim("No commits found"));
1142
1165
  }
1143
1166
  return;
1144
1167
  }
1145
1168
  const commits = commitsByDate.get(targetDate) ?? [];
1146
- console.log(`${chalk13.bold("name:")} ${repoName}`);
1147
- if (config.commit?.conventional && commits.length > 0) {
1148
- const version2 = getVersionAtCommit(commits[0].hash);
1149
- if (version2) {
1150
- console.log(`${chalk13.bold("version:")} ${stripToMinor(version2)}`);
1151
- } else {
1152
- console.log(`${chalk13.bold("version:")} ${chalk13.red("unknown")}`);
1153
- }
1154
- } else if (patchVersion && minorVersion) {
1155
- console.log(
1156
- `${chalk13.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
1157
- );
1158
- } else {
1159
- console.log(`${chalk13.bold("version:")} v0.1 (initial)`);
1160
- }
1161
- console.log(`${chalk13.bold.blue(targetDate)}`);
1169
+ console.log(`${chalk14.bold("name:")} ${repoName}`);
1170
+ displayVersion(
1171
+ !!config.commit?.conventional,
1172
+ commits[0]?.hash,
1173
+ patchVersion,
1174
+ minorVersion
1175
+ );
1176
+ console.log(`${chalk14.bold.blue(targetDate)}`);
1162
1177
  printCommitsWithFiles(commits, ignore2, options.verbose ?? false);
1163
1178
  }
1164
1179
 
1165
1180
  // src/commands/devlog/skip.ts
1166
- import chalk14 from "chalk";
1181
+ import chalk15 from "chalk";
1167
1182
  function skip(date) {
1168
1183
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
1169
- console.log(chalk14.red("Invalid date format. Use YYYY-MM-DD"));
1184
+ console.log(chalk15.red("Invalid date format. Use YYYY-MM-DD"));
1170
1185
  process.exit(1);
1171
1186
  }
1172
1187
  const config = loadConfig();
1173
1188
  const skipDays = config.devlog?.skip?.days ?? [];
1174
1189
  if (skipDays.includes(date)) {
1175
- console.log(chalk14.yellow(`${date} is already in skip list`));
1190
+ console.log(chalk15.yellow(`${date} is already in skip list`));
1176
1191
  return;
1177
1192
  }
1178
1193
  skipDays.push(date);
@@ -1185,27 +1200,27 @@ function skip(date) {
1185
1200
  }
1186
1201
  };
1187
1202
  saveConfig(config);
1188
- console.log(chalk14.green(`Added ${date} to skip list`));
1203
+ console.log(chalk15.green(`Added ${date} to skip list`));
1189
1204
  }
1190
1205
 
1191
1206
  // src/commands/devlog/version.ts
1192
- import chalk15 from "chalk";
1207
+ import chalk16 from "chalk";
1193
1208
  function version() {
1194
1209
  const config = loadConfig();
1195
1210
  const name = getRepoName();
1196
1211
  const lastInfo = getLastVersionInfo(name, config);
1197
1212
  const lastVersion = lastInfo?.version ?? null;
1198
1213
  const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
1199
- console.log(`${chalk15.bold("name:")} ${name}`);
1200
- console.log(`${chalk15.bold("last:")} ${lastVersion ?? chalk15.dim("none")}`);
1201
- console.log(`${chalk15.bold("next:")} ${nextVersion ?? chalk15.dim("none")}`);
1214
+ console.log(`${chalk16.bold("name:")} ${name}`);
1215
+ console.log(`${chalk16.bold("last:")} ${lastVersion ?? chalk16.dim("none")}`);
1216
+ console.log(`${chalk16.bold("next:")} ${nextVersion ?? chalk16.dim("none")}`);
1202
1217
  }
1203
1218
 
1204
1219
  // src/commands/verify/init.ts
1205
- import chalk26 from "chalk";
1220
+ import chalk27 from "chalk";
1206
1221
 
1207
1222
  // src/shared/promptMultiselect.ts
1208
- import chalk16 from "chalk";
1223
+ import chalk17 from "chalk";
1209
1224
  import enquirer3 from "enquirer";
1210
1225
  async function promptMultiselect(message, options) {
1211
1226
  const { selected } = await enquirer3.prompt({
@@ -1214,7 +1229,7 @@ async function promptMultiselect(message, options) {
1214
1229
  message,
1215
1230
  choices: options.map((opt) => ({
1216
1231
  name: opt.value,
1217
- message: `${opt.name} - ${chalk16.dim(opt.description)}`
1232
+ message: `${opt.name} - ${chalk17.dim(opt.description)}`
1218
1233
  })),
1219
1234
  // @ts-expect-error - enquirer types don't include symbols but it's supported
1220
1235
  symbols: {
@@ -1230,7 +1245,7 @@ async function promptMultiselect(message, options) {
1230
1245
  // src/shared/readPackageJson.ts
1231
1246
  import * as fs5 from "fs";
1232
1247
  import * as path3 from "path";
1233
- import chalk17 from "chalk";
1248
+ import chalk18 from "chalk";
1234
1249
  function findPackageJson() {
1235
1250
  const packageJsonPath = path3.join(process.cwd(), "package.json");
1236
1251
  if (fs5.existsSync(packageJsonPath)) {
@@ -1244,7 +1259,7 @@ function readPackageJson(filePath) {
1244
1259
  function requirePackageJson() {
1245
1260
  const packageJsonPath = findPackageJson();
1246
1261
  if (!packageJsonPath) {
1247
- console.error(chalk17.red("No package.json found in current directory"));
1262
+ console.error(chalk18.red("No package.json found in current directory"));
1248
1263
  process.exit(1);
1249
1264
  }
1250
1265
  const pkg = readPackageJson(packageJsonPath);
@@ -1282,13 +1297,13 @@ var expectedScripts = {
1282
1297
  };
1283
1298
 
1284
1299
  // src/commands/verify/setup/setupBuild.ts
1285
- import chalk19 from "chalk";
1300
+ import chalk20 from "chalk";
1286
1301
 
1287
1302
  // src/commands/verify/installPackage.ts
1288
1303
  import { execSync as execSync7 } from "child_process";
1289
1304
  import * as fs6 from "fs";
1290
1305
  import * as path4 from "path";
1291
- import chalk18 from "chalk";
1306
+ import chalk19 from "chalk";
1292
1307
  function writePackageJson(filePath, pkg) {
1293
1308
  fs6.writeFileSync(filePath, `${JSON.stringify(pkg, null, 2)}
1294
1309
  `);
@@ -1303,12 +1318,12 @@ function addScript(pkg, name, command) {
1303
1318
  };
1304
1319
  }
1305
1320
  function installPackage(name, cwd) {
1306
- console.log(chalk18.dim(`Installing ${name}...`));
1321
+ console.log(chalk19.dim(`Installing ${name}...`));
1307
1322
  try {
1308
1323
  execSync7(`npm install -D ${name}`, { stdio: "inherit", cwd });
1309
1324
  return true;
1310
1325
  } catch {
1311
- console.error(chalk18.red(`Failed to install ${name}`));
1326
+ console.error(chalk19.red(`Failed to install ${name}`));
1312
1327
  return false;
1313
1328
  }
1314
1329
  }
@@ -1329,10 +1344,10 @@ function addToKnipIgnoreBinaries(cwd, binary) {
1329
1344
  `${JSON.stringify(knipConfig, null, " ")}
1330
1345
  `
1331
1346
  );
1332
- console.log(chalk18.dim(`Added '${binary}' to knip.json ignoreBinaries`));
1347
+ console.log(chalk19.dim(`Added '${binary}' to knip.json ignoreBinaries`));
1333
1348
  }
1334
1349
  } catch {
1335
- console.log(chalk18.yellow("Warning: Could not update knip.json"));
1350
+ console.log(chalk19.yellow("Warning: Could not update knip.json"));
1336
1351
  }
1337
1352
  }
1338
1353
  function setupVerifyScript(packageJsonPath, scriptName, command) {
@@ -1344,7 +1359,7 @@ function setupVerifyScript(packageJsonPath, scriptName, command) {
1344
1359
 
1345
1360
  // src/commands/verify/setup/setupBuild.ts
1346
1361
  async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
1347
- console.log(chalk19.blue("\nSetting up build verification..."));
1362
+ console.log(chalk20.blue("\nSetting up build verification..."));
1348
1363
  let command;
1349
1364
  if (hasVite && hasTypescript) {
1350
1365
  command = "tsc -b && vite build --logLevel error";
@@ -1353,16 +1368,16 @@ async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
1353
1368
  } else {
1354
1369
  command = "tsc --noEmit";
1355
1370
  }
1356
- console.log(chalk19.dim(`Using: ${command}`));
1371
+ console.log(chalk20.dim(`Using: ${command}`));
1357
1372
  const pkg = readPackageJson(packageJsonPath);
1358
1373
  writePackageJson(packageJsonPath, addScript(pkg, "verify:build", command));
1359
1374
  }
1360
1375
 
1361
1376
  // src/commands/verify/setup/setupDuplicateCode.ts
1362
1377
  import * as path5 from "path";
1363
- import chalk20 from "chalk";
1378
+ import chalk21 from "chalk";
1364
1379
  async function setupDuplicateCode(packageJsonPath) {
1365
- console.log(chalk20.blue("\nSetting up jscpd..."));
1380
+ console.log(chalk21.blue("\nSetting up jscpd..."));
1366
1381
  const cwd = path5.dirname(packageJsonPath);
1367
1382
  const pkg = readPackageJson(packageJsonPath);
1368
1383
  const hasJscpd = !!pkg.dependencies?.jscpd || !!pkg.devDependencies?.jscpd;
@@ -1378,9 +1393,9 @@ async function setupDuplicateCode(packageJsonPath) {
1378
1393
 
1379
1394
  // src/commands/verify/setup/setupHardcodedColors.ts
1380
1395
  import * as path6 from "path";
1381
- import chalk21 from "chalk";
1396
+ import chalk22 from "chalk";
1382
1397
  async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
1383
- console.log(chalk21.blue("\nSetting up hardcoded colors check..."));
1398
+ console.log(chalk22.blue("\nSetting up hardcoded colors check..."));
1384
1399
  const cwd = path6.dirname(packageJsonPath);
1385
1400
  if (!hasOpenColor) {
1386
1401
  installPackage("open-color", cwd);
@@ -1395,9 +1410,9 @@ async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
1395
1410
 
1396
1411
  // src/commands/verify/setup/setupKnip.ts
1397
1412
  import * as path7 from "path";
1398
- import chalk22 from "chalk";
1413
+ import chalk23 from "chalk";
1399
1414
  async function setupKnip(packageJsonPath) {
1400
- console.log(chalk22.blue("\nSetting up knip..."));
1415
+ console.log(chalk23.blue("\nSetting up knip..."));
1401
1416
  const cwd = path7.dirname(packageJsonPath);
1402
1417
  const pkg = readPackageJson(packageJsonPath);
1403
1418
  if (!pkg.devDependencies?.knip && !installPackage("knip", cwd)) {
@@ -1412,14 +1427,14 @@ async function setupKnip(packageJsonPath) {
1412
1427
 
1413
1428
  // src/commands/verify/setup/setupLint.ts
1414
1429
  import * as path8 from "path";
1415
- import chalk24 from "chalk";
1430
+ import chalk25 from "chalk";
1416
1431
 
1417
1432
  // src/commands/lint/init.ts
1418
1433
  import { execSync as execSync9 } from "child_process";
1419
1434
  import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
1420
1435
  import { dirname as dirname6, join as join6 } from "path";
1421
1436
  import { fileURLToPath as fileURLToPath2 } from "url";
1422
- import chalk23 from "chalk";
1437
+ import chalk24 from "chalk";
1423
1438
 
1424
1439
  // src/shared/removeEslint.ts
1425
1440
  import { execSync as execSync8 } from "child_process";
@@ -1525,10 +1540,10 @@ async function init2() {
1525
1540
  console.log("biome.json already has the correct linter config");
1526
1541
  return;
1527
1542
  }
1528
- console.log(chalk23.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
1543
+ console.log(chalk24.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
1529
1544
  console.log();
1530
1545
  printDiff(oldContent, newContent);
1531
- const confirm = await promptConfirm(chalk23.red("Update biome.json?"));
1546
+ const confirm = await promptConfirm(chalk24.red("Update biome.json?"));
1532
1547
  if (!confirm) {
1533
1548
  console.log("Skipped biome.json update");
1534
1549
  return;
@@ -1539,7 +1554,7 @@ async function init2() {
1539
1554
 
1540
1555
  // src/commands/verify/setup/setupLint.ts
1541
1556
  async function setupLint(packageJsonPath) {
1542
- console.log(chalk24.blue("\nSetting up biome..."));
1557
+ console.log(chalk25.blue("\nSetting up biome..."));
1543
1558
  const cwd = path8.dirname(packageJsonPath);
1544
1559
  const pkg = readPackageJson(packageJsonPath);
1545
1560
  if (!pkg.devDependencies?.["@biomejs/biome"]) {
@@ -1557,9 +1572,9 @@ async function setupLint(packageJsonPath) {
1557
1572
 
1558
1573
  // src/commands/verify/setup/setupTest.ts
1559
1574
  import * as path9 from "path";
1560
- import chalk25 from "chalk";
1575
+ import chalk26 from "chalk";
1561
1576
  async function setupTest(packageJsonPath) {
1562
- console.log(chalk25.blue("\nSetting up vitest..."));
1577
+ console.log(chalk26.blue("\nSetting up vitest..."));
1563
1578
  const cwd = path9.dirname(packageJsonPath);
1564
1579
  const pkg = readPackageJson(packageJsonPath);
1565
1580
  if (!pkg.devDependencies?.vitest && !installPackage("vitest", cwd)) {
@@ -1702,16 +1717,16 @@ async function init3() {
1702
1717
  const setup = detectExistingSetup(pkg);
1703
1718
  const availableOptions = getAvailableOptions(setup);
1704
1719
  if (availableOptions.length === 0) {
1705
- console.log(chalk26.green("All verify scripts are already configured!"));
1720
+ console.log(chalk27.green("All verify scripts are already configured!"));
1706
1721
  return;
1707
1722
  }
1708
- console.log(chalk26.bold("Available verify scripts to add:\n"));
1723
+ console.log(chalk27.bold("Available verify scripts to add:\n"));
1709
1724
  const selected = await promptMultiselect(
1710
1725
  "Select verify scripts to add:",
1711
1726
  availableOptions
1712
1727
  );
1713
1728
  if (selected.length === 0) {
1714
- console.log(chalk26.yellow("No scripts selected"));
1729
+ console.log(chalk27.yellow("No scripts selected"));
1715
1730
  return;
1716
1731
  }
1717
1732
  for (const choice of selected) {
@@ -1736,28 +1751,28 @@ async function init3() {
1736
1751
  break;
1737
1752
  }
1738
1753
  }
1739
- console.log(chalk26.green(`
1754
+ console.log(chalk27.green(`
1740
1755
  Added ${selected.length} verify script(s):`));
1741
1756
  for (const choice of selected) {
1742
- console.log(chalk26.green(` - verify:${choice}`));
1757
+ console.log(chalk27.green(` - verify:${choice}`));
1743
1758
  }
1744
- console.log(chalk26.dim("\nRun 'assist verify' to run all verify scripts"));
1759
+ console.log(chalk27.dim("\nRun 'assist verify' to run all verify scripts"));
1745
1760
  }
1746
1761
 
1747
1762
  // src/commands/vscode/init.ts
1748
1763
  import * as fs8 from "fs";
1749
1764
  import * as path11 from "path";
1750
- import chalk28 from "chalk";
1765
+ import chalk29 from "chalk";
1751
1766
 
1752
1767
  // src/commands/vscode/createLaunchJson.ts
1753
1768
  import * as fs7 from "fs";
1754
1769
  import * as path10 from "path";
1755
- import chalk27 from "chalk";
1770
+ import chalk28 from "chalk";
1756
1771
  function ensureVscodeFolder() {
1757
1772
  const vscodeDir = path10.join(process.cwd(), ".vscode");
1758
1773
  if (!fs7.existsSync(vscodeDir)) {
1759
1774
  fs7.mkdirSync(vscodeDir);
1760
- console.log(chalk27.dim("Created .vscode folder"));
1775
+ console.log(chalk28.dim("Created .vscode folder"));
1761
1776
  }
1762
1777
  }
1763
1778
  function removeVscodeFromGitignore() {
@@ -1772,7 +1787,7 @@ function removeVscodeFromGitignore() {
1772
1787
  );
1773
1788
  if (filteredLines.length !== lines.length) {
1774
1789
  fs7.writeFileSync(gitignorePath, filteredLines.join("\n"));
1775
- console.log(chalk27.dim("Removed .vscode references from .gitignore"));
1790
+ console.log(chalk28.dim("Removed .vscode references from .gitignore"));
1776
1791
  }
1777
1792
  }
1778
1793
  function createLaunchJson() {
@@ -1790,7 +1805,7 @@ function createLaunchJson() {
1790
1805
  const launchPath = path10.join(process.cwd(), ".vscode", "launch.json");
1791
1806
  fs7.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
1792
1807
  `);
1793
- console.log(chalk27.green("Created .vscode/launch.json"));
1808
+ console.log(chalk28.green("Created .vscode/launch.json"));
1794
1809
  }
1795
1810
  function createSettingsJson() {
1796
1811
  const settings = {
@@ -1803,7 +1818,7 @@ function createSettingsJson() {
1803
1818
  const settingsPath = path10.join(process.cwd(), ".vscode", "settings.json");
1804
1819
  fs7.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
1805
1820
  `);
1806
- console.log(chalk27.green("Created .vscode/settings.json"));
1821
+ console.log(chalk28.green("Created .vscode/settings.json"));
1807
1822
  }
1808
1823
  function createExtensionsJson() {
1809
1824
  const extensions = {
@@ -1815,7 +1830,7 @@ function createExtensionsJson() {
1815
1830
  `${JSON.stringify(extensions, null, " ")}
1816
1831
  `
1817
1832
  );
1818
- console.log(chalk27.green("Created .vscode/extensions.json"));
1833
+ console.log(chalk28.green("Created .vscode/extensions.json"));
1819
1834
  }
1820
1835
 
1821
1836
  // src/commands/vscode/init.ts
@@ -1847,16 +1862,16 @@ async function init4() {
1847
1862
  });
1848
1863
  }
1849
1864
  if (availableOptions.length === 0) {
1850
- console.log(chalk28.green("VS Code configuration already exists!"));
1865
+ console.log(chalk29.green("VS Code configuration already exists!"));
1851
1866
  return;
1852
1867
  }
1853
- console.log(chalk28.bold("Available VS Code configurations to add:\n"));
1868
+ console.log(chalk29.bold("Available VS Code configurations to add:\n"));
1854
1869
  const selected = await promptMultiselect(
1855
1870
  "Select configurations to add:",
1856
1871
  availableOptions
1857
1872
  );
1858
1873
  if (selected.length === 0) {
1859
- console.log(chalk28.yellow("No configurations selected"));
1874
+ console.log(chalk29.yellow("No configurations selected"));
1860
1875
  return;
1861
1876
  }
1862
1877
  removeVscodeFromGitignore();
@@ -1873,7 +1888,7 @@ async function init4() {
1873
1888
  }
1874
1889
  }
1875
1890
  console.log(
1876
- chalk28.green(`
1891
+ chalk29.green(`
1877
1892
  Added ${selected.length} VS Code configuration(s)`)
1878
1893
  );
1879
1894
  }
@@ -1887,7 +1902,7 @@ async function init5() {
1887
1902
  // src/commands/lint/runFileNameCheck.ts
1888
1903
  import fs10 from "fs";
1889
1904
  import path13 from "path";
1890
- import chalk29 from "chalk";
1905
+ import chalk30 from "chalk";
1891
1906
 
1892
1907
  // src/shared/findSourceFiles.ts
1893
1908
  import fs9 from "fs";
@@ -1939,16 +1954,16 @@ function checkFileNames() {
1939
1954
  function runFileNameCheck() {
1940
1955
  const violations = checkFileNames();
1941
1956
  if (violations.length > 0) {
1942
- console.error(chalk29.red("\nFile name check failed:\n"));
1957
+ console.error(chalk30.red("\nFile name check failed:\n"));
1943
1958
  console.error(
1944
- chalk29.red(
1959
+ chalk30.red(
1945
1960
  " Files without classes or React components should not start with a capital letter.\n"
1946
1961
  )
1947
1962
  );
1948
1963
  for (const violation of violations) {
1949
- console.error(chalk29.red(` ${violation.filePath}`));
1964
+ console.error(chalk30.red(` ${violation.filePath}`));
1950
1965
  console.error(
1951
- chalk29.gray(
1966
+ chalk30.gray(
1952
1967
  ` Rename to: ${violation.fileName.charAt(0).toLowerCase()}${violation.fileName.slice(1)}
1953
1968
  `
1954
1969
  )
@@ -1968,17 +1983,17 @@ function runFileNameCheck() {
1968
1983
  import fs11 from "fs";
1969
1984
 
1970
1985
  // src/commands/lint/shared.ts
1971
- import chalk30 from "chalk";
1986
+ import chalk31 from "chalk";
1972
1987
  function reportViolations(violations, checkName, errorMessage, successMessage) {
1973
1988
  if (violations.length > 0) {
1974
- console.error(chalk30.red(`
1989
+ console.error(chalk31.red(`
1975
1990
  ${checkName} failed:
1976
1991
  `));
1977
- console.error(chalk30.red(` ${errorMessage}
1992
+ console.error(chalk31.red(` ${errorMessage}
1978
1993
  `));
1979
1994
  for (const violation of violations) {
1980
- console.error(chalk30.red(` ${violation.filePath}:${violation.line}`));
1981
- console.error(chalk30.gray(` ${violation.content}
1995
+ console.error(chalk31.red(` ${violation.filePath}:${violation.line}`));
1996
+ console.error(chalk31.gray(` ${violation.content}
1982
1997
  `));
1983
1998
  }
1984
1999
  return false;
@@ -2227,12 +2242,15 @@ async function notify() {
2227
2242
  console.log(`Notification sent: ${notification_type} for ${projectName}`);
2228
2243
  }
2229
2244
 
2230
- // src/commands/prs/shared.ts
2231
- import { execSync as execSync11 } from "child_process";
2245
+ // src/commands/prs/resolveCommentWithReply.ts
2246
+ import { execSync as execSync12 } from "child_process";
2232
2247
  import { existsSync as existsSync11, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
2233
2248
  import { tmpdir } from "os";
2234
2249
  import { join as join9 } from "path";
2235
2250
  import { parse } from "yaml";
2251
+
2252
+ // src/commands/prs/shared.ts
2253
+ import { execSync as execSync11 } from "child_process";
2236
2254
  function isGhNotInstalled(error) {
2237
2255
  if (error instanceof Error) {
2238
2256
  const msg = error.message.toLowerCase();
@@ -2266,8 +2284,10 @@ function getCurrentPrNumber() {
2266
2284
  throw error;
2267
2285
  }
2268
2286
  }
2287
+
2288
+ // src/commands/prs/resolveCommentWithReply.ts
2269
2289
  function replyToComment(org, repo, prNumber, commentId, message) {
2270
- execSync11(
2290
+ execSync12(
2271
2291
  `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
2272
2292
  { stdio: "inherit" }
2273
2293
  );
@@ -2277,7 +2297,7 @@ function resolveThread(threadId) {
2277
2297
  const queryFile = join9(tmpdir(), `gh-mutation-${Date.now()}.graphql`);
2278
2298
  writeFileSync9(queryFile, mutation);
2279
2299
  try {
2280
- execSync11(
2300
+ execSync12(
2281
2301
  `gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
2282
2302
  { stdio: "inherit" }
2283
2303
  );
@@ -2356,7 +2376,7 @@ function fixed(commentId, sha) {
2356
2376
  // src/commands/prs/listComments.ts
2357
2377
  import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync11 } from "fs";
2358
2378
  import { join as join11 } from "path";
2359
- import chalk31 from "chalk";
2379
+ import chalk32 from "chalk";
2360
2380
  import { stringify } from "yaml";
2361
2381
 
2362
2382
  // src/lib/isClaudeCode.ts
@@ -2365,9 +2385,9 @@ function isClaudeCode() {
2365
2385
  }
2366
2386
 
2367
2387
  // src/commands/prs/fetchReviewComments.ts
2368
- import { execSync as execSync12 } from "child_process";
2388
+ import { execSync as execSync13 } from "child_process";
2369
2389
  function fetchReviewComments(org, repo, prNumber) {
2370
- const result = execSync12(
2390
+ const result = execSync13(
2371
2391
  `gh api repos/${org}/${repo}/pulls/${prNumber}/reviews`,
2372
2392
  { encoding: "utf-8" }
2373
2393
  );
@@ -2384,7 +2404,7 @@ function fetchReviewComments(org, repo, prNumber) {
2384
2404
  );
2385
2405
  }
2386
2406
  function fetchLineComments(org, repo, prNumber, threadInfo) {
2387
- const result = execSync12(
2407
+ const result = execSync13(
2388
2408
  `gh api repos/${org}/${repo}/pulls/${prNumber}/comments`,
2389
2409
  { encoding: "utf-8" }
2390
2410
  );
@@ -2410,7 +2430,7 @@ function fetchLineComments(org, repo, prNumber, threadInfo) {
2410
2430
  }
2411
2431
 
2412
2432
  // src/commands/prs/fetchThreadIds.ts
2413
- import { execSync as execSync13 } from "child_process";
2433
+ import { execSync as execSync14 } from "child_process";
2414
2434
  import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "fs";
2415
2435
  import { tmpdir as tmpdir2 } from "os";
2416
2436
  import { join as join10 } from "path";
@@ -2419,7 +2439,7 @@ function fetchThreadIds(org, repo, prNumber) {
2419
2439
  const queryFile = join10(tmpdir2(), `gh-query-${Date.now()}.graphql`);
2420
2440
  writeFileSync10(queryFile, THREAD_QUERY);
2421
2441
  try {
2422
- const result = execSync13(
2442
+ const result = execSync14(
2423
2443
  `gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
2424
2444
  { encoding: "utf-8" }
2425
2445
  );
@@ -2443,17 +2463,17 @@ function fetchThreadIds(org, repo, prNumber) {
2443
2463
  // src/commands/prs/listComments.ts
2444
2464
  function formatForHuman(comment) {
2445
2465
  if (comment.type === "review") {
2446
- const stateColor = comment.state === "APPROVED" ? chalk31.green : comment.state === "CHANGES_REQUESTED" ? chalk31.red : chalk31.yellow;
2466
+ const stateColor = comment.state === "APPROVED" ? chalk32.green : comment.state === "CHANGES_REQUESTED" ? chalk32.red : chalk32.yellow;
2447
2467
  return [
2448
- `${chalk31.cyan("Review")} by ${chalk31.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
2468
+ `${chalk32.cyan("Review")} by ${chalk32.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
2449
2469
  comment.body,
2450
2470
  ""
2451
2471
  ].join("\n");
2452
2472
  }
2453
2473
  const location = comment.line ? `:${comment.line}` : "";
2454
2474
  return [
2455
- `${chalk31.cyan("Line comment")} by ${chalk31.bold(comment.user)} on ${chalk31.dim(`${comment.path}${location}`)}`,
2456
- chalk31.dim(comment.diff_hunk.split("\n").slice(-3).join("\n")),
2475
+ `${chalk32.cyan("Line comment")} by ${chalk32.bold(comment.user)} on ${chalk32.dim(`${comment.path}${location}`)}`,
2476
+ chalk32.dim(comment.diff_hunk.split("\n").slice(-3).join("\n")),
2457
2477
  comment.body,
2458
2478
  ""
2459
2479
  ].join("\n");
@@ -2520,67 +2540,45 @@ async function listComments() {
2520
2540
  }
2521
2541
 
2522
2542
  // src/commands/prs/prs.ts
2523
- import { execSync as execSync14 } from "child_process";
2524
- import chalk32 from "chalk";
2543
+ import { execSync as execSync15 } from "child_process";
2544
+
2545
+ // src/commands/prs/displayPaginated.ts
2546
+ import chalk33 from "chalk";
2525
2547
  import enquirer4 from "enquirer";
2526
2548
  var PAGE_SIZE = 10;
2527
- async function prs(options) {
2528
- const state = options.open ? "open" : options.closed ? "closed" : "all";
2529
- try {
2530
- const result = execSync14(
2531
- `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
2532
- { encoding: "utf-8" }
2549
+ function getStatus(pr) {
2550
+ if (pr.state === "MERGED" && pr.mergedAt) {
2551
+ return { label: chalk33.magenta("merged"), date: pr.mergedAt };
2552
+ }
2553
+ if (pr.state === "CLOSED" && pr.closedAt) {
2554
+ return { label: chalk33.red("closed"), date: pr.closedAt };
2555
+ }
2556
+ return { label: chalk33.green("opened"), date: pr.createdAt };
2557
+ }
2558
+ function displayPage(pullRequests, totalPages, page) {
2559
+ const start = page * PAGE_SIZE;
2560
+ const end = Math.min(start + PAGE_SIZE, pullRequests.length);
2561
+ const pagePrs = pullRequests.slice(start, end);
2562
+ console.log(
2563
+ `
2564
+ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
2565
+ `
2566
+ );
2567
+ for (const pr of pagePrs) {
2568
+ const status = getStatus(pr);
2569
+ const formattedDate = new Date(status.date).toISOString().split("T")[0];
2570
+ const fileCount = pr.changedFiles.toLocaleString();
2571
+ console.log(
2572
+ `${chalk33.cyan(`#${pr.number}`)} ${pr.title} ${chalk33.dim(`(${pr.author.login},`)} ${status.label} ${chalk33.dim(`${formattedDate})`)}`
2533
2573
  );
2534
- const pullRequests = JSON.parse(result);
2535
- if (pullRequests.length === 0) {
2536
- console.log(
2537
- `No ${state === "all" ? "" : `${state} `}pull requests found.`
2538
- );
2539
- return;
2540
- }
2541
- await displayPaginated(pullRequests);
2542
- } catch (error) {
2543
- if (isGhNotInstalled(error)) {
2544
- console.error("Error: GitHub CLI (gh) is not installed.");
2545
- console.error("Install it from https://cli.github.com/");
2546
- return;
2547
- }
2548
- throw error;
2574
+ console.log(chalk33.dim(` ${fileCount} files | ${pr.url}`));
2575
+ console.log();
2549
2576
  }
2550
2577
  }
2551
2578
  async function displayPaginated(pullRequests) {
2552
2579
  const totalPages = Math.ceil(pullRequests.length / PAGE_SIZE);
2553
2580
  let currentPage = 0;
2554
- const getStatus = (pr) => {
2555
- if (pr.state === "MERGED" && pr.mergedAt) {
2556
- return { label: chalk32.magenta("merged"), date: pr.mergedAt };
2557
- }
2558
- if (pr.state === "CLOSED" && pr.closedAt) {
2559
- return { label: chalk32.red("closed"), date: pr.closedAt };
2560
- }
2561
- return { label: chalk32.green("opened"), date: pr.createdAt };
2562
- };
2563
- const displayPage = (page) => {
2564
- const start = page * PAGE_SIZE;
2565
- const end = Math.min(start + PAGE_SIZE, pullRequests.length);
2566
- const pagePrs = pullRequests.slice(start, end);
2567
- console.log(
2568
- `
2569
- Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
2570
- `
2571
- );
2572
- for (const pr of pagePrs) {
2573
- const status = getStatus(pr);
2574
- const formattedDate = new Date(status.date).toISOString().split("T")[0];
2575
- const fileCount = pr.changedFiles.toLocaleString();
2576
- console.log(
2577
- `${chalk32.cyan(`#${pr.number}`)} ${pr.title} ${chalk32.dim(`(${pr.author.login},`)} ${status.label} ${chalk32.dim(`${formattedDate})`)}`
2578
- );
2579
- console.log(chalk32.dim(` ${fileCount} files | ${pr.url}`));
2580
- console.log();
2581
- }
2582
- };
2583
- displayPage(currentPage);
2581
+ displayPage(pullRequests, totalPages, currentPage);
2584
2582
  if (totalPages <= 1) {
2585
2583
  return;
2586
2584
  }
@@ -2599,18 +2597,44 @@ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
2599
2597
  });
2600
2598
  if (action === "Next page") {
2601
2599
  currentPage++;
2602
- displayPage(currentPage);
2600
+ displayPage(pullRequests, totalPages, currentPage);
2603
2601
  } else if (action === "Previous page") {
2604
2602
  currentPage--;
2605
- displayPage(currentPage);
2603
+ displayPage(pullRequests, totalPages, currentPage);
2606
2604
  } else {
2607
2605
  break;
2608
2606
  }
2609
2607
  }
2610
2608
  }
2611
2609
 
2610
+ // src/commands/prs/prs.ts
2611
+ async function prs(options) {
2612
+ const state = options.open ? "open" : options.closed ? "closed" : "all";
2613
+ try {
2614
+ const result = execSync15(
2615
+ `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
2616
+ { encoding: "utf-8" }
2617
+ );
2618
+ const pullRequests = JSON.parse(result);
2619
+ if (pullRequests.length === 0) {
2620
+ console.log(
2621
+ `No ${state === "all" ? "" : `${state} `}pull requests found.`
2622
+ );
2623
+ return;
2624
+ }
2625
+ await displayPaginated(pullRequests);
2626
+ } catch (error) {
2627
+ if (isGhNotInstalled(error)) {
2628
+ console.error("Error: GitHub CLI (gh) is not installed.");
2629
+ console.error("Install it from https://cli.github.com/");
2630
+ return;
2631
+ }
2632
+ throw error;
2633
+ }
2634
+ }
2635
+
2612
2636
  // src/commands/prs/wontfix.ts
2613
- import { execSync as execSync15 } from "child_process";
2637
+ import { execSync as execSync16 } from "child_process";
2614
2638
  function validateReason(reason) {
2615
2639
  const lowerReason = reason.toLowerCase();
2616
2640
  if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
@@ -2627,7 +2651,7 @@ function validateShaReferences(reason) {
2627
2651
  const invalidShas = [];
2628
2652
  for (const sha of shas) {
2629
2653
  try {
2630
- execSync15(`git cat-file -t ${sha}`, { stdio: "pipe" });
2654
+ execSync16(`git cat-file -t ${sha}`, { stdio: "pipe" });
2631
2655
  } catch {
2632
2656
  invalidShas.push(sha);
2633
2657
  }
@@ -2659,7 +2683,7 @@ import { spawn as spawn2 } from "child_process";
2659
2683
  import * as path15 from "path";
2660
2684
 
2661
2685
  // src/commands/refactor/getViolations.ts
2662
- import { execSync as execSync16 } from "child_process";
2686
+ import { execSync as execSync17 } from "child_process";
2663
2687
  import fs15 from "fs";
2664
2688
  import { minimatch as minimatch2 } from "minimatch";
2665
2689
 
@@ -2699,7 +2723,7 @@ function getIgnoredFiles() {
2699
2723
  }
2700
2724
 
2701
2725
  // src/commands/refactor/logViolations.ts
2702
- import chalk33 from "chalk";
2726
+ import chalk34 from "chalk";
2703
2727
  var DEFAULT_MAX_LINES = 100;
2704
2728
  function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
2705
2729
  if (violations.length === 0) {
@@ -2708,43 +2732,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
2708
2732
  }
2709
2733
  return;
2710
2734
  }
2711
- console.error(chalk33.red(`
2735
+ console.error(chalk34.red(`
2712
2736
  Refactor check failed:
2713
2737
  `));
2714
- console.error(chalk33.red(` The following files exceed ${maxLines} lines:
2738
+ console.error(chalk34.red(` The following files exceed ${maxLines} lines:
2715
2739
  `));
2716
2740
  for (const violation of violations) {
2717
- console.error(chalk33.red(` ${violation.file} (${violation.lines} lines)`));
2741
+ console.error(chalk34.red(` ${violation.file} (${violation.lines} lines)`));
2718
2742
  }
2719
2743
  console.error(
2720
- chalk33.yellow(
2744
+ chalk34.yellow(
2721
2745
  `
2722
2746
  Each file needs to be sensibly refactored, or if there is no sensible
2723
2747
  way to refactor it, ignore it with:
2724
2748
  `
2725
2749
  )
2726
2750
  );
2727
- console.error(chalk33.gray(` assist refactor ignore <file>
2751
+ console.error(chalk34.gray(` assist refactor ignore <file>
2728
2752
  `));
2729
2753
  if (process.env.CLAUDECODE) {
2730
- console.error(chalk33.cyan(`
2754
+ console.error(chalk34.cyan(`
2731
2755
  ## Extracting Code to New Files
2732
2756
  `));
2733
2757
  console.error(
2734
- chalk33.cyan(
2758
+ chalk34.cyan(
2735
2759
  ` When extracting logic from one file to another, consider where the extracted code belongs:
2736
2760
  `
2737
2761
  )
2738
2762
  );
2739
2763
  console.error(
2740
- chalk33.cyan(
2764
+ chalk34.cyan(
2741
2765
  ` 1. Keep related logic together: If the extracted code is tightly coupled to the
2742
2766
  original file's domain, create a new folder containing both the original and extracted files.
2743
2767
  `
2744
2768
  )
2745
2769
  );
2746
2770
  console.error(
2747
- chalk33.cyan(
2771
+ chalk34.cyan(
2748
2772
  ` 2. Share common utilities: If the extracted code can be reused across multiple
2749
2773
  domains, move it to a common/shared folder.
2750
2774
  `
@@ -2764,7 +2788,7 @@ function getGitFiles(options) {
2764
2788
  }
2765
2789
  const files = /* @__PURE__ */ new Set();
2766
2790
  if (options.staged || options.modified) {
2767
- const staged = execSync16("git diff --cached --name-only", {
2791
+ const staged = execSync17("git diff --cached --name-only", {
2768
2792
  encoding: "utf-8"
2769
2793
  });
2770
2794
  for (const file of staged.trim().split("\n").filter(Boolean)) {
@@ -2772,7 +2796,7 @@ function getGitFiles(options) {
2772
2796
  }
2773
2797
  }
2774
2798
  if (options.unstaged || options.modified) {
2775
- const unstaged = execSync16("git diff --name-only", { encoding: "utf-8" });
2799
+ const unstaged = execSync17("git diff --name-only", { encoding: "utf-8" });
2776
2800
  for (const file of unstaged.trim().split("\n").filter(Boolean)) {
2777
2801
  files.add(file);
2778
2802
  }
@@ -2861,11 +2885,11 @@ async function check(pattern2, options) {
2861
2885
 
2862
2886
  // src/commands/refactor/ignore.ts
2863
2887
  import fs16 from "fs";
2864
- import chalk34 from "chalk";
2888
+ import chalk35 from "chalk";
2865
2889
  var REFACTOR_YML_PATH2 = "refactor.yml";
2866
2890
  function ignore(file) {
2867
2891
  if (!fs16.existsSync(file)) {
2868
- console.error(chalk34.red(`Error: File does not exist: ${file}`));
2892
+ console.error(chalk35.red(`Error: File does not exist: ${file}`));
2869
2893
  process.exit(1);
2870
2894
  }
2871
2895
  const content = fs16.readFileSync(file, "utf-8");
@@ -2881,12 +2905,430 @@ function ignore(file) {
2881
2905
  fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
2882
2906
  }
2883
2907
  console.log(
2884
- chalk34.green(
2908
+ chalk35.green(
2885
2909
  `Added ${file} to refactor ignore list (max ${maxLines} lines)`
2886
2910
  )
2887
2911
  );
2888
2912
  }
2889
2913
 
2914
+ // src/commands/refactor/restructure/index.ts
2915
+ import path23 from "path";
2916
+ import chalk38 from "chalk";
2917
+
2918
+ // src/commands/refactor/restructure/buildImportGraph.ts
2919
+ import path16 from "path";
2920
+ import ts5 from "typescript";
2921
+ function loadCompilerOptions(tsConfigPath) {
2922
+ const configFile = ts5.readConfigFile(tsConfigPath, ts5.sys.readFile);
2923
+ const parsed = ts5.parseJsonConfigFileContent(
2924
+ configFile.config,
2925
+ ts5.sys,
2926
+ path16.dirname(tsConfigPath)
2927
+ );
2928
+ return parsed.options;
2929
+ }
2930
+ function getImportSpecifiers(sourceFile) {
2931
+ const specifiers = [];
2932
+ const visit = (node) => {
2933
+ if (ts5.isImportDeclaration(node) && ts5.isStringLiteral(node.moduleSpecifier)) {
2934
+ specifiers.push(node.moduleSpecifier.text);
2935
+ } else if (ts5.isExportDeclaration(node) && node.moduleSpecifier && ts5.isStringLiteral(node.moduleSpecifier)) {
2936
+ specifiers.push(node.moduleSpecifier.text);
2937
+ } else if (ts5.isCallExpression(node) && node.expression.kind === ts5.SyntaxKind.ImportKeyword && node.arguments.length === 1 && ts5.isStringLiteral(node.arguments[0])) {
2938
+ specifiers.push(node.arguments[0].text);
2939
+ }
2940
+ ts5.forEachChild(node, visit);
2941
+ };
2942
+ visit(sourceFile);
2943
+ return specifiers;
2944
+ }
2945
+ function buildImportGraph(candidateFiles, tsConfigPath) {
2946
+ const options = loadCompilerOptions(tsConfigPath);
2947
+ const configFile = ts5.readConfigFile(tsConfigPath, ts5.sys.readFile);
2948
+ const parsed = ts5.parseJsonConfigFileContent(
2949
+ configFile.config,
2950
+ ts5.sys,
2951
+ path16.dirname(tsConfigPath)
2952
+ );
2953
+ const program2 = ts5.createProgram(parsed.fileNames, options);
2954
+ const allFiles = /* @__PURE__ */ new Set();
2955
+ const edges = [];
2956
+ const importedBy = /* @__PURE__ */ new Map();
2957
+ const imports = /* @__PURE__ */ new Map();
2958
+ for (const sourceFile of program2.getSourceFiles()) {
2959
+ const filePath = path16.resolve(sourceFile.fileName);
2960
+ if (filePath.includes("node_modules")) continue;
2961
+ allFiles.add(filePath);
2962
+ const specifiers = getImportSpecifiers(sourceFile);
2963
+ for (const specifier of specifiers) {
2964
+ if (!specifier.startsWith(".")) continue;
2965
+ const resolved = ts5.resolveModuleName(
2966
+ specifier,
2967
+ filePath,
2968
+ options,
2969
+ ts5.sys
2970
+ );
2971
+ const resolvedPath = resolved.resolvedModule?.resolvedFileName;
2972
+ if (!resolvedPath || resolvedPath.includes("node_modules")) continue;
2973
+ const absTarget = path16.resolve(resolvedPath);
2974
+ edges.push({ source: filePath, target: absTarget, specifier });
2975
+ const targetSet = importedBy.get(absTarget) ?? /* @__PURE__ */ new Set();
2976
+ if (!importedBy.has(absTarget)) importedBy.set(absTarget, targetSet);
2977
+ targetSet.add(filePath);
2978
+ const sourceSet = imports.get(filePath) ?? /* @__PURE__ */ new Set();
2979
+ if (!imports.has(filePath)) imports.set(filePath, sourceSet);
2980
+ sourceSet.add(absTarget);
2981
+ }
2982
+ }
2983
+ return { files: candidateFiles, edges, importedBy, imports };
2984
+ }
2985
+
2986
+ // src/commands/refactor/restructure/clusterDirectories.ts
2987
+ import path17 from "path";
2988
+ function clusterDirectories(graph) {
2989
+ const dirImportedBy = /* @__PURE__ */ new Map();
2990
+ for (const edge of graph.edges) {
2991
+ const sourceDir = path17.dirname(edge.source);
2992
+ const targetDir = path17.dirname(edge.target);
2993
+ if (sourceDir === targetDir) continue;
2994
+ if (!graph.files.has(edge.target)) continue;
2995
+ const existing = dirImportedBy.get(targetDir) ?? /* @__PURE__ */ new Set();
2996
+ if (!dirImportedBy.has(targetDir)) dirImportedBy.set(targetDir, existing);
2997
+ existing.add(sourceDir);
2998
+ }
2999
+ const clusters = /* @__PURE__ */ new Map();
3000
+ for (const [dir, importers] of dirImportedBy) {
3001
+ if (importers.size !== 1) continue;
3002
+ const parentDir = [...importers][0];
3003
+ if (isAncestor(dir, parentDir)) continue;
3004
+ if (isAncestor(parentDir, dir)) continue;
3005
+ const cluster = clusters.get(parentDir) ?? [];
3006
+ if (!clusters.has(parentDir)) clusters.set(parentDir, cluster);
3007
+ cluster.push(dir);
3008
+ }
3009
+ for (const [parentDir, children] of clusters) {
3010
+ const filtered = children.filter((child) => !clusters.has(child));
3011
+ if (filtered.length === 0) {
3012
+ clusters.delete(parentDir);
3013
+ } else {
3014
+ clusters.set(parentDir, filtered);
3015
+ }
3016
+ }
3017
+ return clusters;
3018
+ }
3019
+ function isAncestor(ancestor, descendant) {
3020
+ const rel = path17.relative(ancestor, descendant);
3021
+ return !rel.startsWith("..") && rel !== "";
3022
+ }
3023
+
3024
+ // src/commands/refactor/restructure/clusterFiles.ts
3025
+ import path18 from "path";
3026
+ function findRootParent(file, importedBy, visited) {
3027
+ const importers = importedBy.get(file);
3028
+ if (!importers || importers.size !== 1) return file;
3029
+ const parent = [...importers][0];
3030
+ const parentDir = path18.dirname(parent);
3031
+ const fileDir = path18.dirname(file);
3032
+ if (parentDir !== fileDir) return file;
3033
+ if (path18.basename(parent, path18.extname(parent)) === "index") return file;
3034
+ if (visited.has(parent)) return file;
3035
+ visited.add(parent);
3036
+ return findRootParent(parent, importedBy, visited);
3037
+ }
3038
+ function clusterFiles(graph) {
3039
+ const clusters = /* @__PURE__ */ new Map();
3040
+ for (const file of graph.files) {
3041
+ const basename6 = path18.basename(file, path18.extname(file));
3042
+ if (basename6 === "index") continue;
3043
+ const importers = graph.importedBy.get(file);
3044
+ if (!importers || importers.size !== 1) continue;
3045
+ const parent = [...importers][0];
3046
+ if (!graph.files.has(parent)) continue;
3047
+ const parentDir = path18.dirname(parent);
3048
+ const fileDir = path18.dirname(file);
3049
+ if (parentDir !== fileDir) continue;
3050
+ const parentBasename = path18.basename(parent, path18.extname(parent));
3051
+ if (parentBasename === "index") continue;
3052
+ const root = findRootParent(parent, graph.importedBy, /* @__PURE__ */ new Set([file]));
3053
+ if (!root || root === file) continue;
3054
+ const cluster = clusters.get(root) ?? [];
3055
+ if (!clusters.has(root)) clusters.set(root, cluster);
3056
+ cluster.push(file);
3057
+ }
3058
+ return clusters;
3059
+ }
3060
+
3061
+ // src/commands/refactor/restructure/computeRewrites.ts
3062
+ import fs17 from "fs";
3063
+ import path19 from "path";
3064
+ function computeRewrites(moves, edges, allProjectFiles) {
3065
+ const moveMap = /* @__PURE__ */ new Map();
3066
+ for (const move of moves) {
3067
+ moveMap.set(move.from, move.to);
3068
+ }
3069
+ const rewrites = [];
3070
+ for (const file of allProjectFiles) {
3071
+ const newFile = moveMap.get(file) ?? file;
3072
+ const edgesFromFile = edges.filter((e) => e.source === file);
3073
+ for (const edge of edgesFromFile) {
3074
+ const newTarget = moveMap.get(edge.target);
3075
+ if (!newTarget && !moveMap.has(file)) continue;
3076
+ const targetPath = newTarget ?? edge.target;
3077
+ const newSpecifier = computeSpecifier(newFile, targetPath);
3078
+ if (newSpecifier === edge.specifier) continue;
3079
+ rewrites.push({
3080
+ file,
3081
+ oldSpecifier: edge.specifier,
3082
+ newSpecifier
3083
+ });
3084
+ }
3085
+ }
3086
+ return rewrites;
3087
+ }
3088
+ function computeSpecifier(fromFile, toFile) {
3089
+ const fromDir = path19.dirname(fromFile);
3090
+ let rel = path19.relative(fromDir, toFile);
3091
+ rel = rel.replace(/\\/g, "/");
3092
+ rel = rel.replace(/\.tsx?$/, "");
3093
+ if (rel.endsWith("/index")) {
3094
+ rel = rel.slice(0, -"/index".length);
3095
+ }
3096
+ if (!rel.startsWith(".")) {
3097
+ rel = `./${rel}`;
3098
+ }
3099
+ return rel;
3100
+ }
3101
+ function applyRewrites(rewrites) {
3102
+ const fileRewrites = /* @__PURE__ */ new Map();
3103
+ for (const rewrite of rewrites) {
3104
+ const existing = fileRewrites.get(rewrite.file) ?? [];
3105
+ if (!fileRewrites.has(rewrite.file))
3106
+ fileRewrites.set(rewrite.file, existing);
3107
+ existing.push(rewrite);
3108
+ }
3109
+ const updatedContents = /* @__PURE__ */ new Map();
3110
+ for (const [file, fileSpecificRewrites] of fileRewrites) {
3111
+ let content = fs17.readFileSync(file, "utf-8");
3112
+ for (const { oldSpecifier, newSpecifier } of fileSpecificRewrites) {
3113
+ const escaped = oldSpecifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3114
+ const pattern2 = new RegExp(`(from\\s+["'])${escaped}(["'])`, "g");
3115
+ content = content.replace(pattern2, `$1${newSpecifier}$2`);
3116
+ }
3117
+ updatedContents.set(file, content);
3118
+ }
3119
+ return updatedContents;
3120
+ }
3121
+
3122
+ // src/commands/refactor/restructure/displayPlan.ts
3123
+ import path20 from "path";
3124
+ import chalk36 from "chalk";
3125
+ function displayPlan(plan) {
3126
+ if (plan.warnings.length > 0) {
3127
+ console.log(chalk36.yellow("\nWarnings:"));
3128
+ for (const warning of plan.warnings) {
3129
+ console.log(chalk36.yellow(` ${warning}`));
3130
+ }
3131
+ }
3132
+ if (plan.newDirectories.length > 0) {
3133
+ console.log(chalk36.bold("\nNew directories:"));
3134
+ for (const dir of plan.newDirectories) {
3135
+ console.log(chalk36.green(` ${dir}/`));
3136
+ }
3137
+ }
3138
+ if (plan.moves.length > 0) {
3139
+ console.log(chalk36.bold("\nFile moves:"));
3140
+ for (const move of plan.moves) {
3141
+ const fromRel = path20.relative(process.cwd(), move.from);
3142
+ const toRel = path20.relative(process.cwd(), move.to);
3143
+ console.log(` ${chalk36.red(fromRel)} \u2192 ${chalk36.green(toRel)}`);
3144
+ console.log(chalk36.dim(` ${move.reason}`));
3145
+ }
3146
+ }
3147
+ if (plan.rewrites.length > 0) {
3148
+ const affectedFiles = new Set(plan.rewrites.map((r) => r.file));
3149
+ console.log(chalk36.bold(`
3150
+ Import rewrites (${affectedFiles.size} files):`));
3151
+ for (const file of affectedFiles) {
3152
+ const fileRewrites = plan.rewrites.filter((r) => r.file === file);
3153
+ const rel = path20.relative(process.cwd(), file);
3154
+ console.log(` ${chalk36.cyan(rel)}:`);
3155
+ for (const { oldSpecifier, newSpecifier } of fileRewrites) {
3156
+ console.log(
3157
+ ` ${chalk36.red(`"${oldSpecifier}"`)} \u2192 ${chalk36.green(`"${newSpecifier}"`)}`
3158
+ );
3159
+ }
3160
+ }
3161
+ }
3162
+ console.log(
3163
+ chalk36.dim(
3164
+ `
3165
+ Summary: ${plan.moves.length} file(s) moved, ${plan.rewrites.length} imports rewritten`
3166
+ )
3167
+ );
3168
+ }
3169
+
3170
+ // src/commands/refactor/restructure/executePlan.ts
3171
+ import fs18 from "fs";
3172
+ import path21 from "path";
3173
+ import chalk37 from "chalk";
3174
+ function executePlan(plan) {
3175
+ const updatedContents = applyRewrites(plan.rewrites);
3176
+ for (const [file, content] of updatedContents) {
3177
+ fs18.writeFileSync(file, content, "utf-8");
3178
+ console.log(
3179
+ chalk37.cyan(` Rewrote imports in ${path21.relative(process.cwd(), file)}`)
3180
+ );
3181
+ }
3182
+ for (const dir of plan.newDirectories) {
3183
+ fs18.mkdirSync(dir, { recursive: true });
3184
+ console.log(chalk37.green(` Created ${path21.relative(process.cwd(), dir)}/`));
3185
+ }
3186
+ for (const move of plan.moves) {
3187
+ const targetDir = path21.dirname(move.to);
3188
+ if (!fs18.existsSync(targetDir)) {
3189
+ fs18.mkdirSync(targetDir, { recursive: true });
3190
+ }
3191
+ fs18.renameSync(move.from, move.to);
3192
+ console.log(
3193
+ chalk37.white(
3194
+ ` Moved ${path21.relative(process.cwd(), move.from)} \u2192 ${path21.relative(process.cwd(), move.to)}`
3195
+ )
3196
+ );
3197
+ }
3198
+ removeEmptyDirectories(plan.moves.map((m) => path21.dirname(m.from)));
3199
+ }
3200
+ function removeEmptyDirectories(dirs) {
3201
+ const unique = [...new Set(dirs)];
3202
+ for (const dir of unique) {
3203
+ if (!fs18.existsSync(dir)) continue;
3204
+ const entries = fs18.readdirSync(dir);
3205
+ if (entries.length === 0) {
3206
+ fs18.rmdirSync(dir);
3207
+ console.log(
3208
+ chalk37.dim(
3209
+ ` Removed empty directory ${path21.relative(process.cwd(), dir)}`
3210
+ )
3211
+ );
3212
+ }
3213
+ }
3214
+ }
3215
+
3216
+ // src/commands/refactor/restructure/planFileMoves.ts
3217
+ import fs19 from "fs";
3218
+ import path22 from "path";
3219
+ function emptyResult() {
3220
+ return { moves: [], directories: [], warnings: [] };
3221
+ }
3222
+ function planFileMoves(clusters) {
3223
+ const result = emptyResult();
3224
+ for (const [parent, children] of clusters) {
3225
+ const parentBase = path22.basename(parent, path22.extname(parent));
3226
+ const newDirName = parentBase;
3227
+ const newDir = path22.join(path22.dirname(parent), newDirName);
3228
+ if (fs19.existsSync(newDir)) {
3229
+ result.warnings.push(
3230
+ `Skipping ${parent}: directory ${newDir} already exists`
3231
+ );
3232
+ continue;
3233
+ }
3234
+ result.directories.push(newDir);
3235
+ result.moves.push({
3236
+ from: parent,
3237
+ to: path22.join(newDir, `index${path22.extname(parent)}`),
3238
+ reason: `Main module of new ${newDirName}/ directory`
3239
+ });
3240
+ for (const child of children) {
3241
+ result.moves.push({
3242
+ from: child,
3243
+ to: path22.join(newDir, path22.basename(child)),
3244
+ reason: `Only imported by ${parentBase}`
3245
+ });
3246
+ }
3247
+ }
3248
+ return result;
3249
+ }
3250
+ function planDirectoryMoves(clusters) {
3251
+ const result = emptyResult();
3252
+ for (const [parentDir, childDirs] of clusters) {
3253
+ for (const childDir of childDirs) {
3254
+ const childName = path22.basename(childDir);
3255
+ const newLocation = path22.join(parentDir, childName);
3256
+ if (fs19.existsSync(newLocation)) {
3257
+ result.warnings.push(
3258
+ `Skipping ${childDir}: ${newLocation} already exists`
3259
+ );
3260
+ continue;
3261
+ }
3262
+ result.directories.push(newLocation);
3263
+ const files = listFilesRecursive(childDir);
3264
+ for (const file of files) {
3265
+ const rel = path22.relative(childDir, file);
3266
+ result.moves.push({
3267
+ from: file,
3268
+ to: path22.join(newLocation, rel),
3269
+ reason: `Directory only imported from ${path22.basename(parentDir)}/`
3270
+ });
3271
+ }
3272
+ }
3273
+ }
3274
+ return result;
3275
+ }
3276
+ function listFilesRecursive(dir) {
3277
+ const results = [];
3278
+ if (!fs19.existsSync(dir)) return results;
3279
+ const entries = fs19.readdirSync(dir, { withFileTypes: true });
3280
+ for (const entry of entries) {
3281
+ const full = path22.join(dir, entry.name);
3282
+ if (entry.isDirectory()) {
3283
+ results.push(...listFilesRecursive(full));
3284
+ } else {
3285
+ results.push(full);
3286
+ }
3287
+ }
3288
+ return results;
3289
+ }
3290
+
3291
+ // src/commands/refactor/restructure/index.ts
3292
+ function buildPlan(candidateFiles, tsConfigPath) {
3293
+ const candidates = new Set(candidateFiles.map((f) => path23.resolve(f)));
3294
+ const graph = buildImportGraph(candidates, tsConfigPath);
3295
+ const allProjectFiles = /* @__PURE__ */ new Set([
3296
+ ...graph.importedBy.keys(),
3297
+ ...graph.imports.keys()
3298
+ ]);
3299
+ const fileClusters = clusterFiles(graph);
3300
+ const dirClusters = clusterDirectories(graph);
3301
+ const fileResult = planFileMoves(fileClusters);
3302
+ const dirResult = planDirectoryMoves(dirClusters);
3303
+ const moves = [...fileResult.moves, ...dirResult.moves];
3304
+ const directories = [...fileResult.directories, ...dirResult.directories];
3305
+ const warnings = [...fileResult.warnings, ...dirResult.warnings];
3306
+ const rewrites = computeRewrites(moves, graph.edges, allProjectFiles);
3307
+ return { moves, rewrites, newDirectories: directories, warnings };
3308
+ }
3309
+ async function restructure(pattern2, options = {}) {
3310
+ const targetPattern = pattern2 ?? "src";
3311
+ const files = findSourceFiles(targetPattern);
3312
+ if (files.length === 0) {
3313
+ console.log(chalk38.yellow("No files found matching pattern"));
3314
+ return;
3315
+ }
3316
+ const tsConfigPath = path23.resolve("tsconfig.json");
3317
+ const plan = buildPlan(files, tsConfigPath);
3318
+ if (plan.moves.length === 0) {
3319
+ console.log(chalk38.green("No restructuring needed"));
3320
+ return;
3321
+ }
3322
+ displayPlan(plan);
3323
+ if (options.apply) {
3324
+ console.log(chalk38.bold("\nApplying changes..."));
3325
+ executePlan(plan);
3326
+ console.log(chalk38.green("\nRestructuring complete"));
3327
+ } else {
3328
+ console.log(chalk38.dim("\nDry run. Use --apply to execute."));
3329
+ }
3330
+ }
3331
+
2890
3332
  // src/commands/run.ts
2891
3333
  import { spawn as spawn3 } from "child_process";
2892
3334
  function run(name, args) {
@@ -2972,29 +3414,29 @@ async function statusLine() {
2972
3414
  }
2973
3415
 
2974
3416
  // src/commands/sync.ts
2975
- import * as fs19 from "fs";
3417
+ import * as fs22 from "fs";
2976
3418
  import * as os from "os";
2977
- import * as path18 from "path";
3419
+ import * as path26 from "path";
2978
3420
  import { fileURLToPath as fileURLToPath3 } from "url";
2979
3421
 
2980
3422
  // src/commands/sync/syncClaudeMd.ts
2981
- import * as fs17 from "fs";
2982
- import * as path16 from "path";
2983
- import chalk35 from "chalk";
3423
+ import * as fs20 from "fs";
3424
+ import * as path24 from "path";
3425
+ import chalk39 from "chalk";
2984
3426
  async function syncClaudeMd(claudeDir, targetBase) {
2985
- const source = path16.join(claudeDir, "CLAUDE.md");
2986
- const target = path16.join(targetBase, "CLAUDE.md");
2987
- const sourceContent = fs17.readFileSync(source, "utf-8");
2988
- if (fs17.existsSync(target)) {
2989
- const targetContent = fs17.readFileSync(target, "utf-8");
3427
+ const source = path24.join(claudeDir, "CLAUDE.md");
3428
+ const target = path24.join(targetBase, "CLAUDE.md");
3429
+ const sourceContent = fs20.readFileSync(source, "utf-8");
3430
+ if (fs20.existsSync(target)) {
3431
+ const targetContent = fs20.readFileSync(target, "utf-8");
2990
3432
  if (sourceContent !== targetContent) {
2991
3433
  console.log(
2992
- chalk35.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
3434
+ chalk39.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
2993
3435
  );
2994
3436
  console.log();
2995
3437
  printDiff(targetContent, sourceContent);
2996
3438
  const confirm = await promptConfirm(
2997
- chalk35.red("Overwrite existing CLAUDE.md?"),
3439
+ chalk39.red("Overwrite existing CLAUDE.md?"),
2998
3440
  false
2999
3441
  );
3000
3442
  if (!confirm) {
@@ -3003,30 +3445,30 @@ async function syncClaudeMd(claudeDir, targetBase) {
3003
3445
  }
3004
3446
  }
3005
3447
  }
3006
- fs17.copyFileSync(source, target);
3448
+ fs20.copyFileSync(source, target);
3007
3449
  console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
3008
3450
  }
3009
3451
 
3010
3452
  // src/commands/sync/syncSettings.ts
3011
- import * as fs18 from "fs";
3012
- import * as path17 from "path";
3013
- import chalk36 from "chalk";
3453
+ import * as fs21 from "fs";
3454
+ import * as path25 from "path";
3455
+ import chalk40 from "chalk";
3014
3456
  async function syncSettings(claudeDir, targetBase) {
3015
- const source = path17.join(claudeDir, "settings.json");
3016
- const target = path17.join(targetBase, "settings.json");
3017
- const sourceContent = fs18.readFileSync(source, "utf-8");
3457
+ const source = path25.join(claudeDir, "settings.json");
3458
+ const target = path25.join(targetBase, "settings.json");
3459
+ const sourceContent = fs21.readFileSync(source, "utf-8");
3018
3460
  const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
3019
- if (fs18.existsSync(target)) {
3020
- const targetContent = fs18.readFileSync(target, "utf-8");
3461
+ if (fs21.existsSync(target)) {
3462
+ const targetContent = fs21.readFileSync(target, "utf-8");
3021
3463
  const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
3022
3464
  if (normalizedSource !== normalizedTarget) {
3023
3465
  console.log(
3024
- chalk36.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
3466
+ chalk40.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
3025
3467
  );
3026
3468
  console.log();
3027
3469
  printDiff(targetContent, sourceContent);
3028
3470
  const confirm = await promptConfirm(
3029
- chalk36.red("Overwrite existing settings.json?"),
3471
+ chalk40.red("Overwrite existing settings.json?"),
3030
3472
  false
3031
3473
  );
3032
3474
  if (!confirm) {
@@ -3035,27 +3477,27 @@ async function syncSettings(claudeDir, targetBase) {
3035
3477
  }
3036
3478
  }
3037
3479
  }
3038
- fs18.copyFileSync(source, target);
3480
+ fs21.copyFileSync(source, target);
3039
3481
  console.log("Copied settings.json to ~/.claude/settings.json");
3040
3482
  }
3041
3483
 
3042
3484
  // src/commands/sync.ts
3043
3485
  var __filename2 = fileURLToPath3(import.meta.url);
3044
- var __dirname4 = path18.dirname(__filename2);
3486
+ var __dirname4 = path26.dirname(__filename2);
3045
3487
  async function sync() {
3046
- const claudeDir = path18.join(__dirname4, "..", "claude");
3047
- const targetBase = path18.join(os.homedir(), ".claude");
3488
+ const claudeDir = path26.join(__dirname4, "..", "claude");
3489
+ const targetBase = path26.join(os.homedir(), ".claude");
3048
3490
  syncCommands(claudeDir, targetBase);
3049
3491
  await syncSettings(claudeDir, targetBase);
3050
3492
  await syncClaudeMd(claudeDir, targetBase);
3051
3493
  }
3052
3494
  function syncCommands(claudeDir, targetBase) {
3053
- const sourceDir = path18.join(claudeDir, "commands");
3054
- const targetDir = path18.join(targetBase, "commands");
3055
- fs19.mkdirSync(targetDir, { recursive: true });
3056
- const files = fs19.readdirSync(sourceDir);
3495
+ const sourceDir = path26.join(claudeDir, "commands");
3496
+ const targetDir = path26.join(targetBase, "commands");
3497
+ fs22.mkdirSync(targetDir, { recursive: true });
3498
+ const files = fs22.readdirSync(sourceDir);
3057
3499
  for (const file of files) {
3058
- fs19.copyFileSync(path18.join(sourceDir, file), path18.join(targetDir, file));
3500
+ fs22.copyFileSync(path26.join(sourceDir, file), path26.join(targetDir, file));
3059
3501
  console.log(`Copied ${file} to ${targetDir}`);
3060
3502
  }
3061
3503
  console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
@@ -3177,6 +3619,27 @@ async function configure() {
3177
3619
  import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
3178
3620
  import { basename as basename4, dirname as dirname11, join as join17 } from "path";
3179
3621
 
3622
+ // src/commands/transcript/cleanText.ts
3623
+ function cleanText(text) {
3624
+ const words = text.split(/\s+/);
3625
+ const cleaned = [];
3626
+ for (let i = 0; i < words.length; i++) {
3627
+ let isRepeat = false;
3628
+ for (let len = 3; len <= 8 && i + len <= words.length; len++) {
3629
+ const phrase = words.slice(i, i + len).join(" ").toLowerCase();
3630
+ const remaining = words.slice(i + len).join(" ").toLowerCase();
3631
+ if (remaining.startsWith(phrase)) {
3632
+ isRepeat = true;
3633
+ break;
3634
+ }
3635
+ }
3636
+ if (!isRepeat) {
3637
+ cleaned.push(words[i]);
3638
+ }
3639
+ }
3640
+ return cleaned.join(" ").replace(/\s+/g, " ").trim();
3641
+ }
3642
+
3180
3643
  // src/commands/transcript/deduplicateCues.ts
3181
3644
  function removeSubstringDuplicates(cues) {
3182
3645
  const toRemove = /* @__PURE__ */ new Set();
@@ -3198,6 +3661,16 @@ function removeSubstringDuplicates(cues) {
3198
3661
  }
3199
3662
  return cues.filter((_, i) => !toRemove.has(i));
3200
3663
  }
3664
+ function findWordOverlap(currentWords, nextWords) {
3665
+ for (let j = Math.min(5, currentWords.length); j >= 1; j--) {
3666
+ const suffix = currentWords.slice(-j).join(" ");
3667
+ const prefix = nextWords.slice(0, j).join(" ");
3668
+ if (suffix === prefix) {
3669
+ return j;
3670
+ }
3671
+ }
3672
+ return 0;
3673
+ }
3201
3674
  function mergeOverlappingCues(cues) {
3202
3675
  if (cues.length === 0) return [];
3203
3676
  const result = [];
@@ -3209,15 +3682,7 @@ function mergeOverlappingCues(cues) {
3209
3682
  if (sameSpeaker && overlaps) {
3210
3683
  const currentWords = current.text.toLowerCase().split(/\s+/);
3211
3684
  const nextWords = next2.text.toLowerCase().split(/\s+/);
3212
- let overlapIndex = -1;
3213
- for (let j = Math.min(5, currentWords.length); j >= 1; j--) {
3214
- const suffix = currentWords.slice(-j).join(" ");
3215
- const prefix = nextWords.slice(0, j).join(" ");
3216
- if (suffix === prefix) {
3217
- overlapIndex = j;
3218
- break;
3219
- }
3220
- }
3685
+ const overlapIndex = findWordOverlap(currentWords, nextWords);
3221
3686
  if (overlapIndex > 0) {
3222
3687
  const nextOriginalWords = next2.text.split(/\s+/);
3223
3688
  current.text = `${current.text} ${nextOriginalWords.slice(overlapIndex).join(" ")}`;
@@ -3233,25 +3698,6 @@ function mergeOverlappingCues(cues) {
3233
3698
  result.push(current);
3234
3699
  return result;
3235
3700
  }
3236
- function cleanText(text) {
3237
- const words = text.split(/\s+/);
3238
- const cleaned = [];
3239
- for (let i = 0; i < words.length; i++) {
3240
- let isRepeat = false;
3241
- for (let len = 3; len <= 8 && i + len <= words.length; len++) {
3242
- const phrase = words.slice(i, i + len).join(" ").toLowerCase();
3243
- const remaining = words.slice(i + len).join(" ").toLowerCase();
3244
- if (remaining.startsWith(phrase)) {
3245
- isRepeat = true;
3246
- break;
3247
- }
3248
- }
3249
- if (!isRepeat) {
3250
- cleaned.push(words[i]);
3251
- }
3252
- }
3253
- return cleaned.join(" ").replace(/\s+/g, " ").trim();
3254
- }
3255
3701
  function deduplicateCues(cues) {
3256
3702
  if (cues.length === 0) return [];
3257
3703
  const sorted = [...cues].sort((a, b) => a.startMs - b.startMs);
@@ -3264,8 +3710,8 @@ function deduplicateCues(cues) {
3264
3710
  }
3265
3711
 
3266
3712
  // src/commands/transcript/parseVtt.ts
3267
- function parseTimestamp(ts5) {
3268
- const parts = ts5.split(":");
3713
+ function parseTimestamp(ts6) {
3714
+ const parts = ts6.split(":");
3269
3715
  let hours = 0;
3270
3716
  let minutes = 0;
3271
3717
  let seconds = 0;
@@ -3411,23 +3857,7 @@ function processFile(inputPath, outputPath) {
3411
3857
  `
3412
3858
  );
3413
3859
  }
3414
- async function format() {
3415
- const { vttDir, transcriptsDir } = getTranscriptConfig();
3416
- if (!existsSync16(vttDir)) {
3417
- console.error(`VTT directory not found: ${vttDir}`);
3418
- process.exit(1);
3419
- }
3420
- if (!existsSync16(transcriptsDir)) {
3421
- mkdirSync5(transcriptsDir, { recursive: true });
3422
- console.log(`Created output directory: ${transcriptsDir}`);
3423
- }
3424
- let vttFiles = findVttFilesRecursive(vttDir);
3425
- if (vttFiles.length === 0) {
3426
- console.log("No VTT files found in vtt directory.");
3427
- return;
3428
- }
3429
- console.log(`Found ${vttFiles.length} VTT file(s) in ${vttDir}
3430
- `);
3860
+ async function fixInvalidDatePrefixes(vttFiles) {
3431
3861
  for (let i = 0; i < vttFiles.length; i++) {
3432
3862
  const vttFile = vttFiles[i];
3433
3863
  if (!isValidDatePrefix(vttFile.filename)) {
@@ -3448,7 +3878,26 @@ async function format() {
3448
3878
  }
3449
3879
  }
3450
3880
  }
3451
- vttFiles = vttFiles.filter((f) => f.absolutePath !== "");
3881
+ return vttFiles.filter((f) => f.absolutePath !== "");
3882
+ }
3883
+ async function format() {
3884
+ const { vttDir, transcriptsDir } = getTranscriptConfig();
3885
+ if (!existsSync16(vttDir)) {
3886
+ console.error(`VTT directory not found: ${vttDir}`);
3887
+ process.exit(1);
3888
+ }
3889
+ if (!existsSync16(transcriptsDir)) {
3890
+ mkdirSync5(transcriptsDir, { recursive: true });
3891
+ console.log(`Created output directory: ${transcriptsDir}`);
3892
+ }
3893
+ let vttFiles = findVttFilesRecursive(vttDir);
3894
+ if (vttFiles.length === 0) {
3895
+ console.log("No VTT files found in vtt directory.");
3896
+ return;
3897
+ }
3898
+ console.log(`Found ${vttFiles.length} VTT file(s) in ${vttDir}
3899
+ `);
3900
+ vttFiles = await fixInvalidDatePrefixes(vttFiles);
3452
3901
  let processed = 0;
3453
3902
  let skipped = 0;
3454
3903
  for (const vttFile of vttFiles) {
@@ -3475,6 +3924,10 @@ Summary: ${processed} processed, ${skipped} skipped`);
3475
3924
  }
3476
3925
 
3477
3926
  // src/commands/transcript/summarise.ts
3927
+ import { existsSync as existsSync18 } from "fs";
3928
+ import { basename as basename5, dirname as dirname13, join as join19, relative as relative2 } from "path";
3929
+
3930
+ // src/commands/transcript/processStagedFile.ts
3478
3931
  import {
3479
3932
  existsSync as existsSync17,
3480
3933
  mkdirSync as mkdirSync6,
@@ -3482,8 +3935,8 @@ import {
3482
3935
  renameSync as renameSync2,
3483
3936
  rmSync
3484
3937
  } from "fs";
3485
- import { basename as basename5, dirname as dirname12, join as join18, relative as relative2 } from "path";
3486
- import chalk37 from "chalk";
3938
+ import { dirname as dirname12, join as join18 } from "path";
3939
+ import chalk41 from "chalk";
3487
3940
  var STAGING_DIR = join18(process.cwd(), ".assist", "transcript");
3488
3941
  var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
3489
3942
  function processStagedFile() {
@@ -3501,7 +3954,7 @@ function processStagedFile() {
3501
3954
  const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
3502
3955
  if (!match) {
3503
3956
  console.error(
3504
- chalk37.red(
3957
+ chalk41.red(
3505
3958
  `Staged file ${stagedFile.filename} missing [Full Transcript](<path>) link on first line.`
3506
3959
  )
3507
3960
  );
@@ -3510,7 +3963,7 @@ function processStagedFile() {
3510
3963
  const contentAfterLink = content.slice(firstLine.length).trim();
3511
3964
  if (!contentAfterLink) {
3512
3965
  console.error(
3513
- chalk37.red(
3966
+ chalk41.red(
3514
3967
  `Staged file ${stagedFile.filename} has no summary content after the transcript link.`
3515
3968
  )
3516
3969
  );
@@ -3523,7 +3976,7 @@ function processStagedFile() {
3523
3976
  );
3524
3977
  if (!matchingTranscript) {
3525
3978
  console.error(
3526
- chalk37.red(
3979
+ chalk41.red(
3527
3980
  `No transcript found matching staged file: ${stagedFile.filename}`
3528
3981
  )
3529
3982
  );
@@ -3542,10 +3995,12 @@ function processStagedFile() {
3542
3995
  }
3543
3996
  return true;
3544
3997
  }
3998
+
3999
+ // src/commands/transcript/summarise.ts
3545
4000
  function summarise() {
3546
4001
  processStagedFile();
3547
4002
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
3548
- if (!existsSync17(transcriptsDir)) {
4003
+ if (!existsSync18(transcriptsDir)) {
3549
4004
  console.log("No transcripts directory found.");
3550
4005
  return;
3551
4006
  }
@@ -3557,16 +4012,16 @@ function summarise() {
3557
4012
  const summaryFiles = findMdFilesRecursive(summaryDir);
3558
4013
  const summaryRelativePaths = new Set(
3559
4014
  summaryFiles.map((f) => {
3560
- const relDir = dirname12(f.relativePath);
4015
+ const relDir = dirname13(f.relativePath);
3561
4016
  const baseName = basename5(f.filename, ".md");
3562
- return relDir === "." ? baseName : join18(relDir, baseName);
4017
+ return relDir === "." ? baseName : join19(relDir, baseName);
3563
4018
  })
3564
4019
  );
3565
4020
  const missing = [];
3566
4021
  for (const transcript of transcriptFiles) {
3567
4022
  const transcriptBaseName = getTranscriptBaseName(transcript.filename);
3568
- const relDir = dirname12(transcript.relativePath);
3569
- const fullKey = relDir === "." ? transcriptBaseName : join18(relDir, transcriptBaseName);
4023
+ const relDir = dirname13(transcript.relativePath);
4024
+ const fullKey = relDir === "." ? transcriptBaseName : join19(relDir, transcriptBaseName);
3570
4025
  if (!summaryRelativePaths.has(fullKey)) {
3571
4026
  missing.push(transcript);
3572
4027
  }
@@ -3577,8 +4032,8 @@ function summarise() {
3577
4032
  }
3578
4033
  const next2 = missing[0];
3579
4034
  const outputFilename = `${getTranscriptBaseName(next2.filename)}.md`;
3580
- const outputPath = join18(STAGING_DIR, outputFilename);
3581
- const summaryFileDir = join18(summaryDir, dirname12(next2.relativePath));
4035
+ const outputPath = join19(STAGING_DIR, outputFilename);
4036
+ const summaryFileDir = join19(summaryDir, dirname13(next2.relativePath));
3582
4037
  const relativeTranscriptPath = encodeURI(
3583
4038
  relative2(summaryFileDir, next2.absolutePath).replace(/\\/g, "/")
3584
4039
  );
@@ -3595,11 +4050,11 @@ function summarise() {
3595
4050
  }
3596
4051
 
3597
4052
  // src/commands/verify/hardcodedColors.ts
3598
- import { execSync as execSync17 } from "child_process";
4053
+ import { execSync as execSync18 } from "child_process";
3599
4054
  var pattern = "0x[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,6}";
3600
4055
  function hardcodedColors() {
3601
4056
  try {
3602
- const output = execSync17(`grep -rEnH '${pattern}' src/`, {
4057
+ const output = execSync18(`grep -rEnH '${pattern}' src/`, {
3603
4058
  encoding: "utf-8"
3604
4059
  });
3605
4060
  const lines = output.trim().split("\n");
@@ -3629,7 +4084,7 @@ Total: ${lines.length} hardcoded color(s)`);
3629
4084
 
3630
4085
  // src/commands/verify/run.ts
3631
4086
  import { spawn as spawn4 } from "child_process";
3632
- import * as path19 from "path";
4087
+ import * as path27 from "path";
3633
4088
  function formatDuration(ms) {
3634
4089
  if (ms < 1e3) {
3635
4090
  return `${ms}ms`;
@@ -3659,7 +4114,7 @@ async function run2(options = {}) {
3659
4114
  return;
3660
4115
  }
3661
4116
  const { packageJsonPath, verifyScripts } = result;
3662
- const packageDir = path19.dirname(packageJsonPath);
4117
+ const packageDir = path27.dirname(packageJsonPath);
3663
4118
  console.log(`Running ${verifyScripts.length} verify script(s) in parallel:`);
3664
4119
  for (const script of verifyScripts) {
3665
4120
  console.log(` - ${script}`);
@@ -3709,7 +4164,7 @@ program.command("init").description("Initialize VS Code and verify configuration
3709
4164
  program.command("commit <message>").description("Create a git commit with validation").action(commit);
3710
4165
  program.command("update").description("Update claude-code to the latest version").action(() => {
3711
4166
  console.log("Updating claude-code...");
3712
- execSync18("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
4167
+ execSync19("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
3713
4168
  });
3714
4169
  var configCommand = program.command("config").description("View and modify assist.yml configuration");
3715
4170
  configCommand.command("set <key> <value>").description("Set a config value (e.g. commit.push true)").action(configSet);
@@ -3742,6 +4197,13 @@ refactorCommand.command("check [pattern]").description("Check for files that exc
3742
4197
  Number.parseInt
3743
4198
  ).action(check);
3744
4199
  refactorCommand.command("ignore <file>").description("Add a file to the refactor ignore list").action(ignore);
4200
+ refactorCommand.command("restructure [pattern]").description(
4201
+ "Analyze import graph and restructure tightly-coupled files into nested directories"
4202
+ ).option("--apply", "Execute the restructuring (default: dry-run)").option(
4203
+ "--max-depth <number>",
4204
+ "Maximum nesting iterations (default: 3)",
4205
+ Number.parseInt
4206
+ ).action(restructure);
3745
4207
  var devlogCommand = program.command("devlog").description("Development log utilities");
3746
4208
  devlogCommand.command("list").description("Group git commits by date").option(
3747
4209
  "--days <number>",