@staff0rd/assist 0.42.1 → 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.1",
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 50",
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
  });
@@ -212,72 +215,159 @@ function commit(message) {
212
215
  import chalk3 from "chalk";
213
216
 
214
217
  // src/commands/complexity/shared.ts
218
+ import fs2 from "fs";
219
+ import path2 from "path";
220
+ import chalk2 from "chalk";
221
+ import ts4 from "typescript";
222
+
223
+ // src/commands/complexity/findSourceFiles.ts
215
224
  import fs from "fs";
216
225
  import path from "path";
217
- import chalk2 from "chalk";
218
226
  import { minimatch } from "minimatch";
219
- import ts3 from "typescript";
227
+ function applyIgnoreGlobs(files) {
228
+ const { complexity } = loadConfig();
229
+ return files.filter(
230
+ (f) => !complexity.ignore.some((glob) => minimatch(f, glob))
231
+ );
232
+ }
233
+ function walk(dir, results) {
234
+ if (!fs.existsSync(dir)) {
235
+ return;
236
+ }
237
+ const extensions = [".ts", ".tsx"];
238
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
239
+ for (const entry of entries) {
240
+ const fullPath = path.join(dir, entry.name);
241
+ if (entry.isDirectory()) {
242
+ if (entry.name !== "node_modules" && entry.name !== ".git") {
243
+ walk(fullPath, results);
244
+ }
245
+ } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
246
+ results.push(fullPath);
247
+ }
248
+ }
249
+ }
250
+ function findSourceFiles(pattern2, baseDir = ".") {
251
+ const results = [];
252
+ if (pattern2.includes("*")) {
253
+ walk(baseDir, results);
254
+ return applyIgnoreGlobs(results.filter((f) => minimatch(f, pattern2)));
255
+ }
256
+ if (fs.existsSync(pattern2) && fs.statSync(pattern2).isFile()) {
257
+ return [pattern2];
258
+ }
259
+ if (fs.existsSync(pattern2) && fs.statSync(pattern2).isDirectory()) {
260
+ walk(pattern2, results);
261
+ return applyIgnoreGlobs(results);
262
+ }
263
+ walk(baseDir, results);
264
+ return applyIgnoreGlobs(results.filter((f) => minimatch(f, pattern2)));
265
+ }
220
266
 
221
- // src/commands/complexity/calculateCyclomaticComplexity.ts
267
+ // src/commands/complexity/getNodeName.ts
222
268
  import ts from "typescript";
269
+ function getNodeName(node) {
270
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
271
+ return node.name?.text ?? "<anonymous>";
272
+ }
273
+ if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) {
274
+ if (ts.isIdentifier(node.name)) {
275
+ return node.name.text;
276
+ }
277
+ if (ts.isStringLiteral(node.name)) {
278
+ return node.name.text;
279
+ }
280
+ return "<computed>";
281
+ }
282
+ if (ts.isArrowFunction(node)) {
283
+ const parent = node.parent;
284
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
285
+ return parent.name.text;
286
+ }
287
+ if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
288
+ return parent.name.text;
289
+ }
290
+ return "<arrow>";
291
+ }
292
+ if (ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
293
+ const prefix = ts.isGetAccessor(node) ? "get " : "set ";
294
+ if (ts.isIdentifier(node.name)) {
295
+ return `${prefix}${node.name.text}`;
296
+ }
297
+ return `${prefix}<computed>`;
298
+ }
299
+ if (ts.isConstructorDeclaration(node)) {
300
+ return "constructor";
301
+ }
302
+ return "<unknown>";
303
+ }
304
+ function hasFunctionBody(node) {
305
+ if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isConstructorDeclaration(node)) {
306
+ return node.body !== void 0;
307
+ }
308
+ return false;
309
+ }
310
+
311
+ // src/commands/complexity/calculateCyclomaticComplexity.ts
312
+ import ts2 from "typescript";
223
313
  var complexityKinds = /* @__PURE__ */ new Set([
224
- ts.SyntaxKind.IfStatement,
225
- ts.SyntaxKind.ForStatement,
226
- ts.SyntaxKind.ForInStatement,
227
- ts.SyntaxKind.ForOfStatement,
228
- ts.SyntaxKind.WhileStatement,
229
- ts.SyntaxKind.DoStatement,
230
- ts.SyntaxKind.CaseClause,
231
- ts.SyntaxKind.CatchClause,
232
- ts.SyntaxKind.ConditionalExpression
314
+ ts2.SyntaxKind.IfStatement,
315
+ ts2.SyntaxKind.ForStatement,
316
+ ts2.SyntaxKind.ForInStatement,
317
+ ts2.SyntaxKind.ForOfStatement,
318
+ ts2.SyntaxKind.WhileStatement,
319
+ ts2.SyntaxKind.DoStatement,
320
+ ts2.SyntaxKind.CaseClause,
321
+ ts2.SyntaxKind.CatchClause,
322
+ ts2.SyntaxKind.ConditionalExpression
233
323
  ]);
234
324
  var logicalOperators = /* @__PURE__ */ new Set([
235
- ts.SyntaxKind.AmpersandAmpersandToken,
236
- ts.SyntaxKind.BarBarToken,
237
- ts.SyntaxKind.QuestionQuestionToken
325
+ ts2.SyntaxKind.AmpersandAmpersandToken,
326
+ ts2.SyntaxKind.BarBarToken,
327
+ ts2.SyntaxKind.QuestionQuestionToken
238
328
  ]);
239
329
  function calculateCyclomaticComplexity(node) {
240
330
  let complexity = 1;
241
331
  function visit(n) {
242
332
  if (complexityKinds.has(n.kind)) {
243
333
  complexity++;
244
- } else if (ts.isBinaryExpression(n) && logicalOperators.has(n.operatorToken.kind)) {
334
+ } else if (ts2.isBinaryExpression(n) && logicalOperators.has(n.operatorToken.kind)) {
245
335
  complexity++;
246
336
  }
247
- ts.forEachChild(n, visit);
337
+ ts2.forEachChild(n, visit);
248
338
  }
249
- ts.forEachChild(node, visit);
339
+ ts2.forEachChild(node, visit);
250
340
  return complexity;
251
341
  }
252
342
 
253
343
  // src/commands/complexity/calculateHalstead.ts
254
- import ts2 from "typescript";
344
+ import ts3 from "typescript";
255
345
  var operatorChecks = [
256
- (n) => ts2.isBinaryExpression(n) ? n.operatorToken.getText() : void 0,
257
- (n) => ts2.isPrefixUnaryExpression(n) || ts2.isPostfixUnaryExpression(n) ? ts2.tokenToString(n.operator) ?? "" : void 0,
258
- (n) => ts2.isCallExpression(n) ? "()" : void 0,
259
- (n) => ts2.isPropertyAccessExpression(n) ? "." : void 0,
260
- (n) => ts2.isElementAccessExpression(n) ? "[]" : void 0,
261
- (n) => ts2.isConditionalExpression(n) ? "?:" : void 0,
262
- (n) => ts2.isReturnStatement(n) ? "return" : void 0,
263
- (n) => ts2.isIfStatement(n) ? "if" : void 0,
264
- (n) => ts2.isForStatement(n) || ts2.isForInStatement(n) || ts2.isForOfStatement(n) ? "for" : void 0,
265
- (n) => ts2.isWhileStatement(n) ? "while" : void 0,
266
- (n) => ts2.isDoStatement(n) ? "do" : void 0,
267
- (n) => ts2.isSwitchStatement(n) ? "switch" : void 0,
268
- (n) => ts2.isCaseClause(n) ? "case" : void 0,
269
- (n) => ts2.isDefaultClause(n) ? "default" : void 0,
270
- (n) => ts2.isBreakStatement(n) ? "break" : void 0,
271
- (n) => ts2.isContinueStatement(n) ? "continue" : void 0,
272
- (n) => ts2.isThrowStatement(n) ? "throw" : void 0,
273
- (n) => ts2.isTryStatement(n) ? "try" : void 0,
274
- (n) => ts2.isCatchClause(n) ? "catch" : void 0,
275
- (n) => ts2.isNewExpression(n) ? "new" : void 0,
276
- (n) => ts2.isTypeOfExpression(n) ? "typeof" : void 0,
277
- (n) => ts2.isAwaitExpression(n) ? "await" : void 0
346
+ (n) => ts3.isBinaryExpression(n) ? n.operatorToken.getText() : void 0,
347
+ (n) => ts3.isPrefixUnaryExpression(n) || ts3.isPostfixUnaryExpression(n) ? ts3.tokenToString(n.operator) ?? "" : void 0,
348
+ (n) => ts3.isCallExpression(n) ? "()" : void 0,
349
+ (n) => ts3.isPropertyAccessExpression(n) ? "." : void 0,
350
+ (n) => ts3.isElementAccessExpression(n) ? "[]" : void 0,
351
+ (n) => ts3.isConditionalExpression(n) ? "?:" : void 0,
352
+ (n) => ts3.isReturnStatement(n) ? "return" : void 0,
353
+ (n) => ts3.isIfStatement(n) ? "if" : void 0,
354
+ (n) => ts3.isForStatement(n) || ts3.isForInStatement(n) || ts3.isForOfStatement(n) ? "for" : void 0,
355
+ (n) => ts3.isWhileStatement(n) ? "while" : void 0,
356
+ (n) => ts3.isDoStatement(n) ? "do" : void 0,
357
+ (n) => ts3.isSwitchStatement(n) ? "switch" : void 0,
358
+ (n) => ts3.isCaseClause(n) ? "case" : void 0,
359
+ (n) => ts3.isDefaultClause(n) ? "default" : void 0,
360
+ (n) => ts3.isBreakStatement(n) ? "break" : void 0,
361
+ (n) => ts3.isContinueStatement(n) ? "continue" : void 0,
362
+ (n) => ts3.isThrowStatement(n) ? "throw" : void 0,
363
+ (n) => ts3.isTryStatement(n) ? "try" : void 0,
364
+ (n) => ts3.isCatchClause(n) ? "catch" : void 0,
365
+ (n) => ts3.isNewExpression(n) ? "new" : void 0,
366
+ (n) => ts3.isTypeOfExpression(n) ? "typeof" : void 0,
367
+ (n) => ts3.isAwaitExpression(n) ? "await" : void 0
278
368
  ];
279
369
  function classifyNode(n, operators, operands) {
280
- if (ts2.isIdentifier(n) || ts2.isNumericLiteral(n) || ts2.isStringLiteral(n)) {
370
+ if (ts3.isIdentifier(n) || ts3.isNumericLiteral(n) || ts3.isStringLiteral(n)) {
281
371
  operands.set(n.text, (operands.get(n.text) ?? 0) + 1);
282
372
  return;
283
373
  }
@@ -314,9 +404,9 @@ function calculateHalstead(node) {
314
404
  const operands = /* @__PURE__ */ new Map();
315
405
  function visit(n) {
316
406
  classifyNode(n, operators, operands);
317
- ts2.forEachChild(n, visit);
407
+ ts3.forEachChild(n, visit);
318
408
  }
319
- ts2.forEachChild(node, visit);
409
+ ts3.forEachChild(node, visit);
320
410
  return computeHalsteadMetrics(operators, operands);
321
411
  }
322
412
 
@@ -358,53 +448,18 @@ function countSloc(content) {
358
448
  }
359
449
 
360
450
  // src/commands/complexity/shared.ts
361
- function getNodeName(node) {
362
- if (ts3.isFunctionDeclaration(node) || ts3.isFunctionExpression(node)) {
363
- return node.name?.text ?? "<anonymous>";
364
- }
365
- if (ts3.isMethodDeclaration(node) || ts3.isMethodSignature(node)) {
366
- if (ts3.isIdentifier(node.name)) {
367
- return node.name.text;
368
- }
369
- if (ts3.isStringLiteral(node.name)) {
370
- return node.name.text;
371
- }
372
- return "<computed>";
373
- }
374
- if (ts3.isArrowFunction(node)) {
375
- const parent = node.parent;
376
- if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) {
377
- return parent.name.text;
378
- }
379
- if (ts3.isPropertyAssignment(parent) && ts3.isIdentifier(parent.name)) {
380
- return parent.name.text;
381
- }
382
- return "<arrow>";
383
- }
384
- if (ts3.isGetAccessor(node) || ts3.isSetAccessor(node)) {
385
- const prefix = ts3.isGetAccessor(node) ? "get " : "set ";
386
- if (ts3.isIdentifier(node.name)) {
387
- return `${prefix}${node.name.text}`;
388
- }
389
- return `${prefix}<computed>`;
390
- }
391
- if (ts3.isConstructorDeclaration(node)) {
392
- return "constructor";
393
- }
394
- return "<unknown>";
395
- }
396
451
  function createSourceFromFile(filePath) {
397
- const content = fs.readFileSync(filePath, "utf-8");
398
- return ts3.createSourceFile(
399
- path.basename(filePath),
452
+ const content = fs2.readFileSync(filePath, "utf-8");
453
+ return ts4.createSourceFile(
454
+ path2.basename(filePath),
400
455
  content,
401
- ts3.ScriptTarget.Latest,
456
+ ts4.ScriptTarget.Latest,
402
457
  true,
403
- filePath.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS
458
+ filePath.endsWith(".tsx") ? ts4.ScriptKind.TSX : ts4.ScriptKind.TS
404
459
  );
405
460
  }
406
461
  function withSourceFiles(pattern2, callback) {
407
- const files = findSourceFilesWithPattern(pattern2);
462
+ const files = findSourceFiles(pattern2);
408
463
  if (files.length === 0) {
409
464
  console.log(chalk2.yellow("No files found matching pattern"));
410
465
  return void 0;
@@ -418,56 +473,11 @@ function forEachFunction(files, callback) {
418
473
  if (hasFunctionBody(node)) {
419
474
  callback(file, getNodeName(node), node);
420
475
  }
421
- ts3.forEachChild(node, visit);
476
+ ts4.forEachChild(node, visit);
422
477
  };
423
478
  visit(sourceFile);
424
479
  }
425
480
  }
426
- function applyIgnoreGlobs(files) {
427
- const { complexity } = loadConfig();
428
- return files.filter(
429
- (f) => !complexity.ignore.some((glob) => minimatch(f, glob))
430
- );
431
- }
432
- function findSourceFilesWithPattern(pattern2, baseDir = ".") {
433
- const results = [];
434
- const extensions = [".ts", ".tsx"];
435
- function walk(dir) {
436
- if (!fs.existsSync(dir)) {
437
- return;
438
- }
439
- const entries = fs.readdirSync(dir, { withFileTypes: true });
440
- for (const entry of entries) {
441
- const fullPath = path.join(dir, entry.name);
442
- if (entry.isDirectory()) {
443
- if (entry.name !== "node_modules" && entry.name !== ".git") {
444
- walk(fullPath);
445
- }
446
- } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
447
- results.push(fullPath);
448
- }
449
- }
450
- }
451
- if (pattern2.includes("*")) {
452
- walk(baseDir);
453
- return applyIgnoreGlobs(results.filter((f) => minimatch(f, pattern2)));
454
- }
455
- if (fs.existsSync(pattern2) && fs.statSync(pattern2).isFile()) {
456
- return [pattern2];
457
- }
458
- if (fs.existsSync(pattern2) && fs.statSync(pattern2).isDirectory()) {
459
- walk(pattern2);
460
- return applyIgnoreGlobs(results);
461
- }
462
- walk(baseDir);
463
- return applyIgnoreGlobs(results.filter((f) => minimatch(f, pattern2)));
464
- }
465
- function hasFunctionBody(node) {
466
- if (ts3.isFunctionDeclaration(node) || ts3.isFunctionExpression(node) || ts3.isArrowFunction(node) || ts3.isMethodDeclaration(node) || ts3.isGetAccessor(node) || ts3.isSetAccessor(node) || ts3.isConstructorDeclaration(node)) {
467
- return node.body !== void 0;
468
- }
469
- return false;
470
- }
471
481
 
472
482
  // src/commands/complexity/cyclomatic.ts
473
483
  async function cyclomatic(pattern2 = "**/*.ts", options = {}) {
@@ -533,7 +543,7 @@ Analyzed ${results.length} functions across ${files.length} files`
533
543
  }
534
544
 
535
545
  // src/commands/complexity/maintainability.ts
536
- import fs2 from "fs";
546
+ import fs3 from "fs";
537
547
  import chalk5 from "chalk";
538
548
  function calculateMaintainabilityIndex(halsteadVolume, cyclomaticComplexity, sloc2) {
539
549
  if (halsteadVolume === 0 || sloc2 === 0) {
@@ -542,69 +552,79 @@ function calculateMaintainabilityIndex(halsteadVolume, cyclomaticComplexity, slo
542
552
  const mi = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(sloc2);
543
553
  return Math.max(0, Math.min(100, mi));
544
554
  }
545
- async function maintainability(pattern2 = "**/*.ts", options = {}) {
546
- withSourceFiles(pattern2, (files) => {
547
- const fileMetrics = /* @__PURE__ */ new Map();
548
- for (const file of files) {
549
- const content = fs2.readFileSync(file, "utf-8");
550
- fileMetrics.set(file, { sloc: countSloc(content), functions: [] });
551
- }
552
- forEachFunction(files, (file, _name, node) => {
553
- const metrics = fileMetrics.get(file);
554
- if (metrics) {
555
- const complexity = calculateCyclomaticComplexity(node);
556
- const halstead2 = calculateHalstead(node);
557
- const mi = calculateMaintainabilityIndex(
558
- halstead2.volume,
559
- complexity,
560
- metrics.sloc
561
- );
562
- metrics.functions.push(mi);
563
- }
564
- });
565
- const results = [];
566
- const { threshold } = options;
567
- for (const [file, metrics] of fileMetrics) {
568
- if (metrics.functions.length === 0) continue;
569
- const avgMaintainability = metrics.functions.reduce((a, b) => a + b, 0) / metrics.functions.length;
570
- const minMaintainability = Math.min(...metrics.functions);
571
- results.push({ file, avgMaintainability, minMaintainability });
572
- }
573
- results.sort((a, b) => a.minMaintainability - b.minMaintainability);
574
- const filtered = threshold !== void 0 ? results.filter((r) => r.minMaintainability < threshold) : results;
575
- if (threshold !== void 0 && filtered.length === 0) {
576
- console.log(chalk5.green("All files pass maintainability threshold"));
577
- } else {
578
- for (const { file, avgMaintainability, minMaintainability } of filtered) {
579
- const color = threshold !== void 0 ? chalk5.red : chalk5.white;
580
- console.log(
581
- `${color(file)} \u2192 avg: ${chalk5.cyan(avgMaintainability.toFixed(1))}, min: ${chalk5.yellow(minMaintainability.toFixed(1))}`
582
- );
583
- }
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);
584
572
  }
585
- console.log(chalk5.dim(`
586
- Analyzed ${results.length} files`));
587
- if (filtered.length > 0 && threshold !== void 0) {
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;
588
594
  console.log(
589
- chalk5.red(
590
- `
591
- Fail: ${filtered.length} file(s) below threshold ${threshold}. Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code.`
592
- )
595
+ `${color(file)} \u2192 avg: ${chalk5.cyan(avgMaintainability.toFixed(1))}, min: ${chalk5.yellow(minMaintainability.toFixed(1))}`
593
596
  );
594
- process.exit(1);
595
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);
596
616
  });
597
617
  }
598
618
 
599
619
  // src/commands/complexity/sloc.ts
600
- import fs3 from "fs";
620
+ import fs4 from "fs";
601
621
  import chalk6 from "chalk";
602
622
  async function sloc(pattern2 = "**/*.ts", options = {}) {
603
623
  withSourceFiles(pattern2, (files) => {
604
624
  const results = [];
605
625
  let hasViolation = false;
606
626
  for (const file of files) {
607
- const content = fs3.readFileSync(file, "utf-8");
627
+ const content = fs4.readFileSync(file, "utf-8");
608
628
  const lines = countSloc(content);
609
629
  results.push({ file, lines });
610
630
  if (options.threshold !== void 0 && lines > options.threshold) {
@@ -631,8 +651,8 @@ Total: ${total} lines across ${files.length} files`)
631
651
  // src/commands/config/index.ts
632
652
  import chalk7 from "chalk";
633
653
  import { stringify as stringifyYaml2 } from "yaml";
634
- function getNestedValue(obj, path19) {
635
- const keys = path19.split(".");
654
+ function getNestedValue(obj, path28) {
655
+ const keys = path28.split(".");
636
656
  let current = obj;
637
657
  for (const key of keys) {
638
658
  if (current === null || current === void 0 || typeof current !== "object") {
@@ -642,8 +662,8 @@ function getNestedValue(obj, path19) {
642
662
  }
643
663
  return current;
644
664
  }
645
- function setNestedValue(obj, path19, value) {
646
- const keys = path19.split(".");
665
+ function setNestedValue(obj, path28, value) {
666
+ const keys = path28.split(".");
647
667
  const result = { ...obj };
648
668
  let current = result;
649
669
  for (let i = 0; i < keys.length - 1; i++) {
@@ -670,8 +690,8 @@ function configSet(key, value) {
670
690
  const result = assistConfigSchema.safeParse(updated);
671
691
  if (!result.success) {
672
692
  for (const issue of result.error.issues) {
673
- const path19 = issue.path.length > 0 ? issue.path.join(".") : key;
674
- console.error(chalk7.red(`${path19}: ${issue.message}`));
693
+ const path28 = issue.path.length > 0 ? issue.path.join(".") : key;
694
+ console.error(chalk7.red(`${path28}: ${issue.message}`));
675
695
  }
676
696
  process.exit(1);
677
697
  }
@@ -698,10 +718,7 @@ function configList() {
698
718
 
699
719
  // src/commands/deploy/init.ts
700
720
  import { execSync as execSync2 } from "child_process";
701
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
702
- import { dirname, join as join2 } from "path";
703
- import { fileURLToPath } from "url";
704
- import chalk9 from "chalk";
721
+ import chalk10 from "chalk";
705
722
  import enquirer2 from "enquirer";
706
723
 
707
724
  // src/shared/promptConfirm.ts
@@ -721,6 +738,12 @@ async function promptConfirm(message, initial = true) {
721
738
  return confirmed;
722
739
  }
723
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
+
724
747
  // src/utils/printDiff.ts
725
748
  import chalk8 from "chalk";
726
749
  import * as diff from "diff";
@@ -750,7 +773,7 @@ function printDiff(oldContent, newContent) {
750
773
  }
751
774
  }
752
775
 
753
- // src/commands/deploy/init.ts
776
+ // src/commands/deploy/updateWorkflow.ts
754
777
  var WORKFLOW_PATH = ".github/workflows/build.yml";
755
778
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
756
779
  function getExistingSiteId() {
@@ -791,11 +814,13 @@ async function updateWorkflow(siteId) {
791
814
  console.log(chalk9.green(`
792
815
  Created ${WORKFLOW_PATH}`));
793
816
  }
817
+
818
+ // src/commands/deploy/init.ts
794
819
  async function init() {
795
- console.log(chalk9.bold("Initializing Netlify deployment...\n"));
820
+ console.log(chalk10.bold("Initializing Netlify deployment...\n"));
796
821
  const existingSiteId = getExistingSiteId();
797
822
  if (existingSiteId) {
798
- console.log(chalk9.dim(`Using existing site ID: ${existingSiteId}
823
+ console.log(chalk10.dim(`Using existing site ID: ${existingSiteId}
799
824
  `));
800
825
  await updateWorkflow(existingSiteId);
801
826
  return;
@@ -807,10 +832,10 @@ async function init() {
807
832
  });
808
833
  } catch (error) {
809
834
  if (error instanceof Error && error.message.includes("command not found")) {
810
- console.error(chalk9.red("\nNetlify CLI is not installed.\n"));
835
+ console.error(chalk10.red("\nNetlify CLI is not installed.\n"));
811
836
  const install = await promptConfirm("Would you like to install it now?");
812
837
  if (install) {
813
- console.log(chalk9.dim("\nInstalling netlify-cli...\n"));
838
+ console.log(chalk10.dim("\nInstalling netlify-cli...\n"));
814
839
  execSync2("npm install -g netlify-cli", { stdio: "inherit" });
815
840
  console.log();
816
841
  execSync2("netlify sites:create --disable-linking", {
@@ -818,7 +843,7 @@ async function init() {
818
843
  });
819
844
  } else {
820
845
  console.log(
821
- chalk9.yellow(
846
+ chalk10.yellow(
822
847
  "\nInstall it manually with: npm install -g netlify-cli\n"
823
848
  )
824
849
  );
@@ -835,17 +860,17 @@ async function init() {
835
860
  validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
836
861
  });
837
862
  await updateWorkflow(siteId);
838
- console.log(chalk9.bold("\nDeployment initialized successfully!"));
863
+ console.log(chalk10.bold("\nDeployment initialized successfully!"));
839
864
  console.log(
840
- chalk9.yellow("\nTo complete setup, create a personal access token at:")
865
+ chalk10.yellow("\nTo complete setup, create a personal access token at:")
841
866
  );
842
867
  console.log(
843
- chalk9.cyan(
868
+ chalk10.cyan(
844
869
  "https://app.netlify.com/user/applications#personal-access-tokens"
845
870
  )
846
871
  );
847
872
  console.log(
848
- chalk9.yellow(
873
+ chalk10.yellow(
849
874
  "\nThen add it as NETLIFY_AUTH_TOKEN in your GitHub repository secrets."
850
875
  )
851
876
  );
@@ -853,7 +878,7 @@ async function init() {
853
878
 
854
879
  // src/commands/deploy/redirect.ts
855
880
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
856
- import chalk10 from "chalk";
881
+ import chalk11 from "chalk";
857
882
  var TRAILING_SLASH_SCRIPT = ` <script>
858
883
  if (!window.location.pathname.endsWith('/')) {
859
884
  window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
@@ -862,32 +887,32 @@ var TRAILING_SLASH_SCRIPT = ` <script>
862
887
  function redirect() {
863
888
  const indexPath = "index.html";
864
889
  if (!existsSync3(indexPath)) {
865
- console.log(chalk10.yellow("No index.html found"));
890
+ console.log(chalk11.yellow("No index.html found"));
866
891
  return;
867
892
  }
868
893
  const content = readFileSync3(indexPath, "utf-8");
869
894
  if (content.includes("window.location.pathname.endsWith('/')")) {
870
- console.log(chalk10.dim("Trailing slash script already present"));
895
+ console.log(chalk11.dim("Trailing slash script already present"));
871
896
  return;
872
897
  }
873
898
  const headCloseIndex = content.indexOf("</head>");
874
899
  if (headCloseIndex === -1) {
875
- 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"));
876
901
  return;
877
902
  }
878
903
  const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
879
904
  writeFileSync3(indexPath, newContent);
880
- console.log(chalk10.green("Added trailing slash redirect to index.html"));
905
+ console.log(chalk11.green("Added trailing slash redirect to index.html"));
881
906
  }
882
907
 
883
908
  // src/commands/devlog/list.ts
884
909
  import { execSync as execSync4 } from "child_process";
885
910
  import { basename as basename2 } from "path";
886
- import chalk12 from "chalk";
911
+ import chalk13 from "chalk";
887
912
 
888
913
  // src/commands/devlog/shared.ts
889
914
  import { execSync as execSync3 } from "child_process";
890
- import chalk11 from "chalk";
915
+ import chalk12 from "chalk";
891
916
 
892
917
  // src/commands/devlog/loadDevlogEntries.ts
893
918
  import { readdirSync, readFileSync as readFileSync4 } from "fs";
@@ -948,17 +973,35 @@ function shouldIgnoreCommit(files, ignorePaths) {
948
973
  }
949
974
  function printCommitsWithFiles(commits, ignore2, verbose) {
950
975
  for (const commit2 of commits) {
951
- console.log(` ${chalk11.yellow(commit2.hash)} ${commit2.message}`);
976
+ console.log(` ${chalk12.yellow(commit2.hash)} ${commit2.message}`);
952
977
  if (verbose) {
953
978
  const visibleFiles = commit2.files.filter(
954
979
  (file) => !ignore2.some((p) => file.startsWith(p))
955
980
  );
956
981
  for (const file of visibleFiles) {
957
- console.log(` ${chalk11.dim(file)}`);
982
+ console.log(` ${chalk12.dim(file)}`);
958
983
  }
959
984
  }
960
985
  }
961
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
+ }
962
1005
 
963
1006
  // src/commands/devlog/list.ts
964
1007
  function list(options) {
@@ -974,22 +1017,7 @@ function list(options) {
974
1017
  `git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
975
1018
  { encoding: "utf-8" }
976
1019
  );
977
- const lines = output.trim().split("\n");
978
- const commits = [];
979
- for (const line of lines) {
980
- const [date, hash, ...messageParts] = line.split("|");
981
- const message = messageParts.join("|");
982
- const files = getCommitFiles(hash);
983
- if (!shouldIgnoreCommit(files, ignore2)) {
984
- commits.push({ date, hash, message, files });
985
- }
986
- }
987
- const commitsByDate = /* @__PURE__ */ new Map();
988
- for (const commit2 of commits) {
989
- const existing = commitsByDate.get(commit2.date) || [];
990
- existing.push(commit2);
991
- commitsByDate.set(commit2.date, existing);
992
- }
1020
+ const commitsByDate = parseGitLogCommits(output, ignore2);
993
1021
  let dateCount = 0;
994
1022
  let isFirst = true;
995
1023
  for (const [date, dateCommits] of commitsByDate) {
@@ -1007,12 +1035,12 @@ function list(options) {
1007
1035
  isFirst = false;
1008
1036
  const entries = devlogEntries.get(date);
1009
1037
  if (skipDays.has(date)) {
1010
- console.log(`${chalk12.bold.blue(date)} ${chalk12.dim("skipped")}`);
1038
+ console.log(`${chalk13.bold.blue(date)} ${chalk13.dim("skipped")}`);
1011
1039
  } else if (entries && entries.length > 0) {
1012
- const entryInfo = entries.map((e) => `${chalk12.green(e.version)} ${e.title}`).join(" | ");
1013
- 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}`);
1014
1042
  } else {
1015
- console.log(`${chalk12.bold.blue(date)} ${chalk12.red("\u26A0 devlog missing")}`);
1043
+ console.log(`${chalk13.bold.blue(date)} ${chalk13.red("\u26A0 devlog missing")}`);
1016
1044
  }
1017
1045
  printCommitsWithFiles(dateCommits, ignore2, options.verbose ?? false);
1018
1046
  }
@@ -1020,7 +1048,7 @@ function list(options) {
1020
1048
 
1021
1049
  // src/commands/devlog/next.ts
1022
1050
  import { execSync as execSync6 } from "child_process";
1023
- import chalk13 from "chalk";
1051
+ import chalk14 from "chalk";
1024
1052
 
1025
1053
  // src/commands/devlog/getLastVersionInfo.ts
1026
1054
  import { execSync as execSync5 } from "child_process";
@@ -1097,6 +1125,22 @@ function bumpVersion(version2, type) {
1097
1125
  }
1098
1126
 
1099
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
+ }
1100
1144
  function next(options) {
1101
1145
  const config = loadConfig();
1102
1146
  const ignore2 = options.ignore ?? config.devlog?.ignore ?? [];
@@ -1110,62 +1154,40 @@ function next(options) {
1110
1154
  "git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
1111
1155
  { encoding: "utf-8" }
1112
1156
  );
1113
- const lines = output.trim().split("\n");
1114
- const commitsByDate = /* @__PURE__ */ new Map();
1115
- for (const line of lines) {
1116
- const [date, hash, ...messageParts] = line.split("|");
1117
- const message = messageParts.join("|");
1118
- if (lastDate && date <= lastDate) {
1119
- continue;
1120
- }
1121
- const files = getCommitFiles(hash);
1122
- if (!shouldIgnoreCommit(files, ignore2)) {
1123
- const existing = commitsByDate.get(date) || [];
1124
- existing.push({ date, hash, message, files });
1125
- commitsByDate.set(date, existing);
1126
- }
1127
- }
1157
+ const commitsByDate = parseGitLogCommits(output, ignore2, lastDate);
1128
1158
  const dates = Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort();
1129
1159
  const targetDate = dates[0];
1130
1160
  if (!targetDate) {
1131
1161
  if (lastInfo) {
1132
- console.log(chalk13.dim("No commits after last versioned entry"));
1162
+ console.log(chalk14.dim("No commits after last versioned entry"));
1133
1163
  } else {
1134
- console.log(chalk13.dim("No commits found"));
1164
+ console.log(chalk14.dim("No commits found"));
1135
1165
  }
1136
1166
  return;
1137
1167
  }
1138
1168
  const commits = commitsByDate.get(targetDate) ?? [];
1139
- console.log(`${chalk13.bold("name:")} ${repoName}`);
1140
- if (config.commit?.conventional && commits.length > 0) {
1141
- const version2 = getVersionAtCommit(commits[0].hash);
1142
- if (version2) {
1143
- console.log(`${chalk13.bold("version:")} ${stripToMinor(version2)}`);
1144
- } else {
1145
- console.log(`${chalk13.bold("version:")} ${chalk13.red("unknown")}`);
1146
- }
1147
- } else if (patchVersion && minorVersion) {
1148
- console.log(
1149
- `${chalk13.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
1150
- );
1151
- } else {
1152
- console.log(`${chalk13.bold("version:")} v0.1 (initial)`);
1153
- }
1154
- 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)}`);
1155
1177
  printCommitsWithFiles(commits, ignore2, options.verbose ?? false);
1156
1178
  }
1157
1179
 
1158
1180
  // src/commands/devlog/skip.ts
1159
- import chalk14 from "chalk";
1181
+ import chalk15 from "chalk";
1160
1182
  function skip(date) {
1161
1183
  if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
1162
- console.log(chalk14.red("Invalid date format. Use YYYY-MM-DD"));
1184
+ console.log(chalk15.red("Invalid date format. Use YYYY-MM-DD"));
1163
1185
  process.exit(1);
1164
1186
  }
1165
1187
  const config = loadConfig();
1166
1188
  const skipDays = config.devlog?.skip?.days ?? [];
1167
1189
  if (skipDays.includes(date)) {
1168
- console.log(chalk14.yellow(`${date} is already in skip list`));
1190
+ console.log(chalk15.yellow(`${date} is already in skip list`));
1169
1191
  return;
1170
1192
  }
1171
1193
  skipDays.push(date);
@@ -1178,27 +1200,27 @@ function skip(date) {
1178
1200
  }
1179
1201
  };
1180
1202
  saveConfig(config);
1181
- console.log(chalk14.green(`Added ${date} to skip list`));
1203
+ console.log(chalk15.green(`Added ${date} to skip list`));
1182
1204
  }
1183
1205
 
1184
1206
  // src/commands/devlog/version.ts
1185
- import chalk15 from "chalk";
1207
+ import chalk16 from "chalk";
1186
1208
  function version() {
1187
1209
  const config = loadConfig();
1188
1210
  const name = getRepoName();
1189
1211
  const lastInfo = getLastVersionInfo(name, config);
1190
1212
  const lastVersion = lastInfo?.version ?? null;
1191
1213
  const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
1192
- console.log(`${chalk15.bold("name:")} ${name}`);
1193
- console.log(`${chalk15.bold("last:")} ${lastVersion ?? chalk15.dim("none")}`);
1194
- 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")}`);
1195
1217
  }
1196
1218
 
1197
1219
  // src/commands/verify/init.ts
1198
- import chalk26 from "chalk";
1220
+ import chalk27 from "chalk";
1199
1221
 
1200
1222
  // src/shared/promptMultiselect.ts
1201
- import chalk16 from "chalk";
1223
+ import chalk17 from "chalk";
1202
1224
  import enquirer3 from "enquirer";
1203
1225
  async function promptMultiselect(message, options) {
1204
1226
  const { selected } = await enquirer3.prompt({
@@ -1207,7 +1229,7 @@ async function promptMultiselect(message, options) {
1207
1229
  message,
1208
1230
  choices: options.map((opt) => ({
1209
1231
  name: opt.value,
1210
- message: `${opt.name} - ${chalk16.dim(opt.description)}`
1232
+ message: `${opt.name} - ${chalk17.dim(opt.description)}`
1211
1233
  })),
1212
1234
  // @ts-expect-error - enquirer types don't include symbols but it's supported
1213
1235
  symbols: {
@@ -1221,23 +1243,23 @@ async function promptMultiselect(message, options) {
1221
1243
  }
1222
1244
 
1223
1245
  // src/shared/readPackageJson.ts
1224
- import * as fs4 from "fs";
1225
- import * as path2 from "path";
1226
- import chalk17 from "chalk";
1227
- function findPackageJson() {
1228
- const packageJsonPath = path2.join(process.cwd(), "package.json");
1229
- if (fs4.existsSync(packageJsonPath)) {
1230
- return packageJsonPath;
1246
+ import * as fs5 from "fs";
1247
+ import * as path3 from "path";
1248
+ import chalk18 from "chalk";
1249
+ function findPackageJson() {
1250
+ const packageJsonPath = path3.join(process.cwd(), "package.json");
1251
+ if (fs5.existsSync(packageJsonPath)) {
1252
+ return packageJsonPath;
1231
1253
  }
1232
1254
  return null;
1233
1255
  }
1234
1256
  function readPackageJson(filePath) {
1235
- return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
1257
+ return JSON.parse(fs5.readFileSync(filePath, "utf-8"));
1236
1258
  }
1237
1259
  function requirePackageJson() {
1238
1260
  const packageJsonPath = findPackageJson();
1239
1261
  if (!packageJsonPath) {
1240
- console.error(chalk17.red("No package.json found in current directory"));
1262
+ console.error(chalk18.red("No package.json found in current directory"));
1241
1263
  process.exit(1);
1242
1264
  }
1243
1265
  const pkg = readPackageJson(packageJsonPath);
@@ -1246,9 +1268,9 @@ function requirePackageJson() {
1246
1268
  function findPackageJsonWithVerifyScripts(startDir) {
1247
1269
  let currentDir = startDir;
1248
1270
  while (true) {
1249
- const packageJsonPath = path2.join(currentDir, "package.json");
1250
- if (fs4.existsSync(packageJsonPath)) {
1251
- const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
1271
+ const packageJsonPath = path3.join(currentDir, "package.json");
1272
+ if (fs5.existsSync(packageJsonPath)) {
1273
+ const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
1252
1274
  const scripts = packageJson.scripts || {};
1253
1275
  const verifyScripts = Object.keys(scripts).filter(
1254
1276
  (name) => name.startsWith("verify:")
@@ -1257,7 +1279,7 @@ function findPackageJsonWithVerifyScripts(startDir) {
1257
1279
  return { packageJsonPath, verifyScripts };
1258
1280
  }
1259
1281
  }
1260
- const parentDir = path2.dirname(currentDir);
1282
+ const parentDir = path3.dirname(currentDir);
1261
1283
  if (parentDir === currentDir) {
1262
1284
  return null;
1263
1285
  }
@@ -1275,15 +1297,15 @@ var expectedScripts = {
1275
1297
  };
1276
1298
 
1277
1299
  // src/commands/verify/setup/setupBuild.ts
1278
- import chalk19 from "chalk";
1300
+ import chalk20 from "chalk";
1279
1301
 
1280
1302
  // src/commands/verify/installPackage.ts
1281
1303
  import { execSync as execSync7 } from "child_process";
1282
- import * as fs5 from "fs";
1283
- import * as path3 from "path";
1284
- import chalk18 from "chalk";
1304
+ import * as fs6 from "fs";
1305
+ import * as path4 from "path";
1306
+ import chalk19 from "chalk";
1285
1307
  function writePackageJson(filePath, pkg) {
1286
- fs5.writeFileSync(filePath, `${JSON.stringify(pkg, null, 2)}
1308
+ fs6.writeFileSync(filePath, `${JSON.stringify(pkg, null, 2)}
1287
1309
  `);
1288
1310
  }
1289
1311
  function addScript(pkg, name, command) {
@@ -1296,36 +1318,36 @@ function addScript(pkg, name, command) {
1296
1318
  };
1297
1319
  }
1298
1320
  function installPackage(name, cwd) {
1299
- console.log(chalk18.dim(`Installing ${name}...`));
1321
+ console.log(chalk19.dim(`Installing ${name}...`));
1300
1322
  try {
1301
1323
  execSync7(`npm install -D ${name}`, { stdio: "inherit", cwd });
1302
1324
  return true;
1303
1325
  } catch {
1304
- console.error(chalk18.red(`Failed to install ${name}`));
1326
+ console.error(chalk19.red(`Failed to install ${name}`));
1305
1327
  return false;
1306
1328
  }
1307
1329
  }
1308
1330
  function addToKnipIgnoreBinaries(cwd, binary) {
1309
- const knipJsonPath = path3.join(cwd, "knip.json");
1331
+ const knipJsonPath = path4.join(cwd, "knip.json");
1310
1332
  try {
1311
1333
  let knipConfig;
1312
- if (fs5.existsSync(knipJsonPath)) {
1313
- knipConfig = JSON.parse(fs5.readFileSync(knipJsonPath, "utf-8"));
1334
+ if (fs6.existsSync(knipJsonPath)) {
1335
+ knipConfig = JSON.parse(fs6.readFileSync(knipJsonPath, "utf-8"));
1314
1336
  } else {
1315
1337
  knipConfig = { $schema: "https://unpkg.com/knip@5/schema.json" };
1316
1338
  }
1317
1339
  const ignoreBinaries = knipConfig.ignoreBinaries ?? [];
1318
1340
  if (!ignoreBinaries.includes(binary)) {
1319
1341
  knipConfig.ignoreBinaries = [...ignoreBinaries, binary];
1320
- fs5.writeFileSync(
1342
+ fs6.writeFileSync(
1321
1343
  knipJsonPath,
1322
1344
  `${JSON.stringify(knipConfig, null, " ")}
1323
1345
  `
1324
1346
  );
1325
- console.log(chalk18.dim(`Added '${binary}' to knip.json ignoreBinaries`));
1347
+ console.log(chalk19.dim(`Added '${binary}' to knip.json ignoreBinaries`));
1326
1348
  }
1327
1349
  } catch {
1328
- console.log(chalk18.yellow("Warning: Could not update knip.json"));
1350
+ console.log(chalk19.yellow("Warning: Could not update knip.json"));
1329
1351
  }
1330
1352
  }
1331
1353
  function setupVerifyScript(packageJsonPath, scriptName, command) {
@@ -1337,7 +1359,7 @@ function setupVerifyScript(packageJsonPath, scriptName, command) {
1337
1359
 
1338
1360
  // src/commands/verify/setup/setupBuild.ts
1339
1361
  async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
1340
- console.log(chalk19.blue("\nSetting up build verification..."));
1362
+ console.log(chalk20.blue("\nSetting up build verification..."));
1341
1363
  let command;
1342
1364
  if (hasVite && hasTypescript) {
1343
1365
  command = "tsc -b && vite build --logLevel error";
@@ -1346,17 +1368,17 @@ async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
1346
1368
  } else {
1347
1369
  command = "tsc --noEmit";
1348
1370
  }
1349
- console.log(chalk19.dim(`Using: ${command}`));
1371
+ console.log(chalk20.dim(`Using: ${command}`));
1350
1372
  const pkg = readPackageJson(packageJsonPath);
1351
1373
  writePackageJson(packageJsonPath, addScript(pkg, "verify:build", command));
1352
1374
  }
1353
1375
 
1354
1376
  // src/commands/verify/setup/setupDuplicateCode.ts
1355
- import * as path4 from "path";
1356
- import chalk20 from "chalk";
1377
+ import * as path5 from "path";
1378
+ import chalk21 from "chalk";
1357
1379
  async function setupDuplicateCode(packageJsonPath) {
1358
- console.log(chalk20.blue("\nSetting up jscpd..."));
1359
- const cwd = path4.dirname(packageJsonPath);
1380
+ console.log(chalk21.blue("\nSetting up jscpd..."));
1381
+ const cwd = path5.dirname(packageJsonPath);
1360
1382
  const pkg = readPackageJson(packageJsonPath);
1361
1383
  const hasJscpd = !!pkg.dependencies?.jscpd || !!pkg.devDependencies?.jscpd;
1362
1384
  if (!hasJscpd && !installPackage("jscpd", cwd)) {
@@ -1370,11 +1392,11 @@ async function setupDuplicateCode(packageJsonPath) {
1370
1392
  }
1371
1393
 
1372
1394
  // src/commands/verify/setup/setupHardcodedColors.ts
1373
- import * as path5 from "path";
1374
- import chalk21 from "chalk";
1395
+ import * as path6 from "path";
1396
+ import chalk22 from "chalk";
1375
1397
  async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
1376
- console.log(chalk21.blue("\nSetting up hardcoded colors check..."));
1377
- const cwd = path5.dirname(packageJsonPath);
1398
+ console.log(chalk22.blue("\nSetting up hardcoded colors check..."));
1399
+ const cwd = path6.dirname(packageJsonPath);
1378
1400
  if (!hasOpenColor) {
1379
1401
  installPackage("open-color", cwd);
1380
1402
  }
@@ -1387,11 +1409,11 @@ async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
1387
1409
  }
1388
1410
 
1389
1411
  // src/commands/verify/setup/setupKnip.ts
1390
- import * as path6 from "path";
1391
- import chalk22 from "chalk";
1412
+ import * as path7 from "path";
1413
+ import chalk23 from "chalk";
1392
1414
  async function setupKnip(packageJsonPath) {
1393
- console.log(chalk22.blue("\nSetting up knip..."));
1394
- const cwd = path6.dirname(packageJsonPath);
1415
+ console.log(chalk23.blue("\nSetting up knip..."));
1416
+ const cwd = path7.dirname(packageJsonPath);
1395
1417
  const pkg = readPackageJson(packageJsonPath);
1396
1418
  if (!pkg.devDependencies?.knip && !installPackage("knip", cwd)) {
1397
1419
  return;
@@ -1404,15 +1426,15 @@ async function setupKnip(packageJsonPath) {
1404
1426
  }
1405
1427
 
1406
1428
  // src/commands/verify/setup/setupLint.ts
1407
- import * as path7 from "path";
1408
- import chalk24 from "chalk";
1429
+ import * as path8 from "path";
1430
+ import chalk25 from "chalk";
1409
1431
 
1410
1432
  // src/commands/lint/init.ts
1411
1433
  import { execSync as execSync9 } from "child_process";
1412
1434
  import { existsSync as existsSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
1413
1435
  import { dirname as dirname6, join as join6 } from "path";
1414
1436
  import { fileURLToPath as fileURLToPath2 } from "url";
1415
- import chalk23 from "chalk";
1437
+ import chalk24 from "chalk";
1416
1438
 
1417
1439
  // src/shared/removeEslint.ts
1418
1440
  import { execSync as execSync8 } from "child_process";
@@ -1518,10 +1540,10 @@ async function init2() {
1518
1540
  console.log("biome.json already has the correct linter config");
1519
1541
  return;
1520
1542
  }
1521
- 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:"));
1522
1544
  console.log();
1523
1545
  printDiff(oldContent, newContent);
1524
- const confirm = await promptConfirm(chalk23.red("Update biome.json?"));
1546
+ const confirm = await promptConfirm(chalk24.red("Update biome.json?"));
1525
1547
  if (!confirm) {
1526
1548
  console.log("Skipped biome.json update");
1527
1549
  return;
@@ -1532,8 +1554,8 @@ async function init2() {
1532
1554
 
1533
1555
  // src/commands/verify/setup/setupLint.ts
1534
1556
  async function setupLint(packageJsonPath) {
1535
- console.log(chalk24.blue("\nSetting up biome..."));
1536
- const cwd = path7.dirname(packageJsonPath);
1557
+ console.log(chalk25.blue("\nSetting up biome..."));
1558
+ const cwd = path8.dirname(packageJsonPath);
1537
1559
  const pkg = readPackageJson(packageJsonPath);
1538
1560
  if (!pkg.devDependencies?.["@biomejs/biome"]) {
1539
1561
  if (!installPackage("@biomejs/biome", cwd)) {
@@ -1549,11 +1571,11 @@ async function setupLint(packageJsonPath) {
1549
1571
  }
1550
1572
 
1551
1573
  // src/commands/verify/setup/setupTest.ts
1552
- import * as path8 from "path";
1553
- import chalk25 from "chalk";
1574
+ import * as path9 from "path";
1575
+ import chalk26 from "chalk";
1554
1576
  async function setupTest(packageJsonPath) {
1555
- console.log(chalk25.blue("\nSetting up vitest..."));
1556
- const cwd = path8.dirname(packageJsonPath);
1577
+ console.log(chalk26.blue("\nSetting up vitest..."));
1578
+ const cwd = path9.dirname(packageJsonPath);
1557
1579
  const pkg = readPackageJson(packageJsonPath);
1558
1580
  if (!pkg.devDependencies?.vitest && !installPackage("vitest", cwd)) {
1559
1581
  return;
@@ -1695,16 +1717,16 @@ async function init3() {
1695
1717
  const setup = detectExistingSetup(pkg);
1696
1718
  const availableOptions = getAvailableOptions(setup);
1697
1719
  if (availableOptions.length === 0) {
1698
- console.log(chalk26.green("All verify scripts are already configured!"));
1720
+ console.log(chalk27.green("All verify scripts are already configured!"));
1699
1721
  return;
1700
1722
  }
1701
- console.log(chalk26.bold("Available verify scripts to add:\n"));
1723
+ console.log(chalk27.bold("Available verify scripts to add:\n"));
1702
1724
  const selected = await promptMultiselect(
1703
1725
  "Select verify scripts to add:",
1704
1726
  availableOptions
1705
1727
  );
1706
1728
  if (selected.length === 0) {
1707
- console.log(chalk26.yellow("No scripts selected"));
1729
+ console.log(chalk27.yellow("No scripts selected"));
1708
1730
  return;
1709
1731
  }
1710
1732
  for (const choice of selected) {
@@ -1729,43 +1751,43 @@ async function init3() {
1729
1751
  break;
1730
1752
  }
1731
1753
  }
1732
- console.log(chalk26.green(`
1754
+ console.log(chalk27.green(`
1733
1755
  Added ${selected.length} verify script(s):`));
1734
1756
  for (const choice of selected) {
1735
- console.log(chalk26.green(` - verify:${choice}`));
1757
+ console.log(chalk27.green(` - verify:${choice}`));
1736
1758
  }
1737
- 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"));
1738
1760
  }
1739
1761
 
1740
1762
  // src/commands/vscode/init.ts
1763
+ import * as fs8 from "fs";
1764
+ import * as path11 from "path";
1765
+ import chalk29 from "chalk";
1766
+
1767
+ // src/commands/vscode/createLaunchJson.ts
1741
1768
  import * as fs7 from "fs";
1742
1769
  import * as path10 from "path";
1743
1770
  import chalk28 from "chalk";
1744
-
1745
- // src/commands/vscode/createLaunchJson.ts
1746
- import * as fs6 from "fs";
1747
- import * as path9 from "path";
1748
- import chalk27 from "chalk";
1749
1771
  function ensureVscodeFolder() {
1750
- const vscodeDir = path9.join(process.cwd(), ".vscode");
1751
- if (!fs6.existsSync(vscodeDir)) {
1752
- fs6.mkdirSync(vscodeDir);
1753
- console.log(chalk27.dim("Created .vscode folder"));
1772
+ const vscodeDir = path10.join(process.cwd(), ".vscode");
1773
+ if (!fs7.existsSync(vscodeDir)) {
1774
+ fs7.mkdirSync(vscodeDir);
1775
+ console.log(chalk28.dim("Created .vscode folder"));
1754
1776
  }
1755
1777
  }
1756
1778
  function removeVscodeFromGitignore() {
1757
- const gitignorePath = path9.join(process.cwd(), ".gitignore");
1758
- if (!fs6.existsSync(gitignorePath)) {
1779
+ const gitignorePath = path10.join(process.cwd(), ".gitignore");
1780
+ if (!fs7.existsSync(gitignorePath)) {
1759
1781
  return;
1760
1782
  }
1761
- const content = fs6.readFileSync(gitignorePath, "utf-8");
1783
+ const content = fs7.readFileSync(gitignorePath, "utf-8");
1762
1784
  const lines = content.split("\n");
1763
1785
  const filteredLines = lines.filter(
1764
1786
  (line) => !line.trim().toLowerCase().includes(".vscode")
1765
1787
  );
1766
1788
  if (filteredLines.length !== lines.length) {
1767
- fs6.writeFileSync(gitignorePath, filteredLines.join("\n"));
1768
- console.log(chalk27.dim("Removed .vscode references from .gitignore"));
1789
+ fs7.writeFileSync(gitignorePath, filteredLines.join("\n"));
1790
+ console.log(chalk28.dim("Removed .vscode references from .gitignore"));
1769
1791
  }
1770
1792
  }
1771
1793
  function createLaunchJson() {
@@ -1780,10 +1802,10 @@ function createLaunchJson() {
1780
1802
  }
1781
1803
  ]
1782
1804
  };
1783
- const launchPath = path9.join(process.cwd(), ".vscode", "launch.json");
1784
- fs6.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
1805
+ const launchPath = path10.join(process.cwd(), ".vscode", "launch.json");
1806
+ fs7.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
1785
1807
  `);
1786
- console.log(chalk27.green("Created .vscode/launch.json"));
1808
+ console.log(chalk28.green("Created .vscode/launch.json"));
1787
1809
  }
1788
1810
  function createSettingsJson() {
1789
1811
  const settings = {
@@ -1793,31 +1815,31 @@ function createSettingsJson() {
1793
1815
  "source.organizeImports.biome": "explicit"
1794
1816
  }
1795
1817
  };
1796
- const settingsPath = path9.join(process.cwd(), ".vscode", "settings.json");
1797
- fs6.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
1818
+ const settingsPath = path10.join(process.cwd(), ".vscode", "settings.json");
1819
+ fs7.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
1798
1820
  `);
1799
- console.log(chalk27.green("Created .vscode/settings.json"));
1821
+ console.log(chalk28.green("Created .vscode/settings.json"));
1800
1822
  }
1801
1823
  function createExtensionsJson() {
1802
1824
  const extensions = {
1803
1825
  recommendations: ["biomejs.biome"]
1804
1826
  };
1805
- const extensionsPath = path9.join(process.cwd(), ".vscode", "extensions.json");
1806
- fs6.writeFileSync(
1827
+ const extensionsPath = path10.join(process.cwd(), ".vscode", "extensions.json");
1828
+ fs7.writeFileSync(
1807
1829
  extensionsPath,
1808
1830
  `${JSON.stringify(extensions, null, " ")}
1809
1831
  `
1810
1832
  );
1811
- console.log(chalk27.green("Created .vscode/extensions.json"));
1833
+ console.log(chalk28.green("Created .vscode/extensions.json"));
1812
1834
  }
1813
1835
 
1814
1836
  // src/commands/vscode/init.ts
1815
1837
  function detectExistingSetup2(pkg) {
1816
- const vscodeDir = path10.join(process.cwd(), ".vscode");
1838
+ const vscodeDir = path11.join(process.cwd(), ".vscode");
1817
1839
  return {
1818
- hasVscodeFolder: fs7.existsSync(vscodeDir),
1819
- hasLaunchJson: fs7.existsSync(path10.join(vscodeDir, "launch.json")),
1820
- hasSettingsJson: fs7.existsSync(path10.join(vscodeDir, "settings.json")),
1840
+ hasVscodeFolder: fs8.existsSync(vscodeDir),
1841
+ hasLaunchJson: fs8.existsSync(path11.join(vscodeDir, "launch.json")),
1842
+ hasSettingsJson: fs8.existsSync(path11.join(vscodeDir, "settings.json")),
1821
1843
  hasVite: !!pkg.devDependencies?.vite || !!pkg.dependencies?.vite
1822
1844
  };
1823
1845
  }
@@ -1840,16 +1862,16 @@ async function init4() {
1840
1862
  });
1841
1863
  }
1842
1864
  if (availableOptions.length === 0) {
1843
- console.log(chalk28.green("VS Code configuration already exists!"));
1865
+ console.log(chalk29.green("VS Code configuration already exists!"));
1844
1866
  return;
1845
1867
  }
1846
- console.log(chalk28.bold("Available VS Code configurations to add:\n"));
1868
+ console.log(chalk29.bold("Available VS Code configurations to add:\n"));
1847
1869
  const selected = await promptMultiselect(
1848
1870
  "Select configurations to add:",
1849
1871
  availableOptions
1850
1872
  );
1851
1873
  if (selected.length === 0) {
1852
- console.log(chalk28.yellow("No configurations selected"));
1874
+ console.log(chalk29.yellow("No configurations selected"));
1853
1875
  return;
1854
1876
  }
1855
1877
  removeVscodeFromGitignore();
@@ -1866,7 +1888,7 @@ async function init4() {
1866
1888
  }
1867
1889
  }
1868
1890
  console.log(
1869
- chalk28.green(`
1891
+ chalk29.green(`
1870
1892
  Added ${selected.length} VS Code configuration(s)`)
1871
1893
  );
1872
1894
  }
@@ -1878,25 +1900,25 @@ async function init5() {
1878
1900
  }
1879
1901
 
1880
1902
  // src/commands/lint/runFileNameCheck.ts
1881
- import fs9 from "fs";
1882
- import path12 from "path";
1883
- import chalk29 from "chalk";
1903
+ import fs10 from "fs";
1904
+ import path13 from "path";
1905
+ import chalk30 from "chalk";
1884
1906
 
1885
1907
  // src/shared/findSourceFiles.ts
1886
- import fs8 from "fs";
1887
- import path11 from "path";
1908
+ import fs9 from "fs";
1909
+ import path12 from "path";
1888
1910
  var EXTENSIONS = [".ts", ".tsx"];
1889
- function findSourceFiles(dir, options = {}) {
1911
+ function findSourceFiles2(dir, options = {}) {
1890
1912
  const { includeTests = true } = options;
1891
1913
  const results = [];
1892
- if (!fs8.existsSync(dir)) {
1914
+ if (!fs9.existsSync(dir)) {
1893
1915
  return results;
1894
1916
  }
1895
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
1917
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
1896
1918
  for (const entry of entries) {
1897
- const fullPath = path11.join(dir, entry.name);
1919
+ const fullPath = path12.join(dir, entry.name);
1898
1920
  if (entry.isDirectory() && entry.name !== "node_modules") {
1899
- results.push(...findSourceFiles(fullPath, options));
1921
+ results.push(...findSourceFiles2(fullPath, options));
1900
1922
  } else if (entry.isFile() && EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
1901
1923
  if (!includeTests && entry.name.includes(".test.")) {
1902
1924
  continue;
@@ -1915,13 +1937,13 @@ function hasClassOrComponent(content) {
1915
1937
  return classPattern.test(content) || functionComponentPattern.test(content) || arrowComponentPattern.test(content);
1916
1938
  }
1917
1939
  function checkFileNames() {
1918
- const sourceFiles = findSourceFiles("src");
1940
+ const sourceFiles = findSourceFiles2("src");
1919
1941
  const violations = [];
1920
1942
  for (const filePath of sourceFiles) {
1921
- const fileName = path12.basename(filePath);
1943
+ const fileName = path13.basename(filePath);
1922
1944
  const nameWithoutExt = fileName.replace(/\.(ts|tsx)$/, "");
1923
1945
  if (/^[A-Z]/.test(nameWithoutExt)) {
1924
- const content = fs9.readFileSync(filePath, "utf-8");
1946
+ const content = fs10.readFileSync(filePath, "utf-8");
1925
1947
  if (!hasClassOrComponent(content)) {
1926
1948
  violations.push({ filePath, fileName });
1927
1949
  }
@@ -1932,16 +1954,16 @@ function checkFileNames() {
1932
1954
  function runFileNameCheck() {
1933
1955
  const violations = checkFileNames();
1934
1956
  if (violations.length > 0) {
1935
- console.error(chalk29.red("\nFile name check failed:\n"));
1957
+ console.error(chalk30.red("\nFile name check failed:\n"));
1936
1958
  console.error(
1937
- chalk29.red(
1959
+ chalk30.red(
1938
1960
  " Files without classes or React components should not start with a capital letter.\n"
1939
1961
  )
1940
1962
  );
1941
1963
  for (const violation of violations) {
1942
- console.error(chalk29.red(` ${violation.filePath}`));
1964
+ console.error(chalk30.red(` ${violation.filePath}`));
1943
1965
  console.error(
1944
- chalk29.gray(
1966
+ chalk30.gray(
1945
1967
  ` Rename to: ${violation.fileName.charAt(0).toLowerCase()}${violation.fileName.slice(1)}
1946
1968
  `
1947
1969
  )
@@ -1958,20 +1980,20 @@ function runFileNameCheck() {
1958
1980
  }
1959
1981
 
1960
1982
  // src/commands/lint/runImportExtensionCheck.ts
1961
- import fs10 from "fs";
1983
+ import fs11 from "fs";
1962
1984
 
1963
1985
  // src/commands/lint/shared.ts
1964
- import chalk30 from "chalk";
1986
+ import chalk31 from "chalk";
1965
1987
  function reportViolations(violations, checkName, errorMessage, successMessage) {
1966
1988
  if (violations.length > 0) {
1967
- console.error(chalk30.red(`
1989
+ console.error(chalk31.red(`
1968
1990
  ${checkName} failed:
1969
1991
  `));
1970
- console.error(chalk30.red(` ${errorMessage}
1992
+ console.error(chalk31.red(` ${errorMessage}
1971
1993
  `));
1972
1994
  for (const violation of violations) {
1973
- console.error(chalk30.red(` ${violation.filePath}:${violation.line}`));
1974
- console.error(chalk30.gray(` ${violation.content}
1995
+ console.error(chalk31.red(` ${violation.filePath}:${violation.line}`));
1996
+ console.error(chalk31.gray(` ${violation.content}
1975
1997
  `));
1976
1998
  }
1977
1999
  return false;
@@ -1984,7 +2006,7 @@ ${checkName} failed:
1984
2006
 
1985
2007
  // src/commands/lint/runImportExtensionCheck.ts
1986
2008
  function checkForImportExtensions(filePath) {
1987
- const content = fs10.readFileSync(filePath, "utf-8");
2009
+ const content = fs11.readFileSync(filePath, "utf-8");
1988
2010
  const lines = content.split("\n");
1989
2011
  const violations = [];
1990
2012
  const importExtensionPattern = /from\s+["']\..*\.(js|ts)["']/;
@@ -2001,7 +2023,7 @@ function checkForImportExtensions(filePath) {
2001
2023
  return violations;
2002
2024
  }
2003
2025
  function checkImportExtensions() {
2004
- const sourceFiles = findSourceFiles("src");
2026
+ const sourceFiles = findSourceFiles2("src");
2005
2027
  const violations = [];
2006
2028
  for (const filePath of sourceFiles) {
2007
2029
  violations.push(...checkForImportExtensions(filePath));
@@ -2018,9 +2040,9 @@ function runImportExtensionCheck() {
2018
2040
  }
2019
2041
 
2020
2042
  // src/commands/lint/runStaticImportCheck.ts
2021
- import fs11 from "fs";
2043
+ import fs12 from "fs";
2022
2044
  function checkForDynamicImports(filePath) {
2023
- const content = fs11.readFileSync(filePath, "utf-8");
2045
+ const content = fs12.readFileSync(filePath, "utf-8");
2024
2046
  const lines = content.split("\n");
2025
2047
  const violations = [];
2026
2048
  const requirePattern = /\brequire\s*\(/;
@@ -2038,7 +2060,7 @@ function checkForDynamicImports(filePath) {
2038
2060
  return violations;
2039
2061
  }
2040
2062
  function checkStaticImports() {
2041
- const sourceFiles = findSourceFiles("src");
2063
+ const sourceFiles = findSourceFiles2("src");
2042
2064
  const violations = [];
2043
2065
  for (const filePath of sourceFiles) {
2044
2066
  violations.push(...checkForDynamicImports(filePath));
@@ -2134,19 +2156,19 @@ function detectPlatform() {
2134
2156
 
2135
2157
  // src/commands/notify/showWindowsNotificationFromWsl.ts
2136
2158
  import { spawn } from "child_process";
2137
- import fs12 from "fs";
2159
+ import fs13 from "fs";
2138
2160
  import { createRequire } from "module";
2139
- import path13 from "path";
2161
+ import path14 from "path";
2140
2162
  var require2 = createRequire(import.meta.url);
2141
2163
  function getSnoreToastPath() {
2142
- const notifierPath = path13.dirname(require2.resolve("node-notifier"));
2143
- return path13.join(notifierPath, "vendor", "snoreToast", "snoretoast-x64.exe");
2164
+ const notifierPath = path14.dirname(require2.resolve("node-notifier"));
2165
+ return path14.join(notifierPath, "vendor", "snoreToast", "snoretoast-x64.exe");
2144
2166
  }
2145
2167
  function showWindowsNotificationFromWsl(options) {
2146
2168
  const { title, message, sound } = options;
2147
2169
  const snoreToastPath = getSnoreToastPath();
2148
2170
  try {
2149
- fs12.chmodSync(snoreToastPath, 493);
2171
+ fs13.chmodSync(snoreToastPath, 493);
2150
2172
  } catch {
2151
2173
  }
2152
2174
  const args = ["-t", title, "-m", message];
@@ -2220,12 +2242,15 @@ async function notify() {
2220
2242
  console.log(`Notification sent: ${notification_type} for ${projectName}`);
2221
2243
  }
2222
2244
 
2223
- // src/commands/prs/shared.ts
2224
- import { execSync as execSync11 } from "child_process";
2245
+ // src/commands/prs/resolveCommentWithReply.ts
2246
+ import { execSync as execSync12 } from "child_process";
2225
2247
  import { existsSync as existsSync11, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
2226
2248
  import { tmpdir } from "os";
2227
2249
  import { join as join9 } from "path";
2228
2250
  import { parse } from "yaml";
2251
+
2252
+ // src/commands/prs/shared.ts
2253
+ import { execSync as execSync11 } from "child_process";
2229
2254
  function isGhNotInstalled(error) {
2230
2255
  if (error instanceof Error) {
2231
2256
  const msg = error.message.toLowerCase();
@@ -2259,8 +2284,10 @@ function getCurrentPrNumber() {
2259
2284
  throw error;
2260
2285
  }
2261
2286
  }
2287
+
2288
+ // src/commands/prs/resolveCommentWithReply.ts
2262
2289
  function replyToComment(org, repo, prNumber, commentId, message) {
2263
- execSync11(
2290
+ execSync12(
2264
2291
  `gh api repos/${org}/${repo}/pulls/${prNumber}/comments -f body="${message.replace(/"/g, '\\"')}" -F in_reply_to=${commentId}`,
2265
2292
  { stdio: "inherit" }
2266
2293
  );
@@ -2270,7 +2297,7 @@ function resolveThread(threadId) {
2270
2297
  const queryFile = join9(tmpdir(), `gh-mutation-${Date.now()}.graphql`);
2271
2298
  writeFileSync9(queryFile, mutation);
2272
2299
  try {
2273
- execSync11(
2300
+ execSync12(
2274
2301
  `gh api graphql -F query=@${queryFile} -f threadId="${threadId}"`,
2275
2302
  { stdio: "inherit" }
2276
2303
  );
@@ -2347,10 +2374,9 @@ function fixed(commentId, sha) {
2347
2374
  }
2348
2375
 
2349
2376
  // src/commands/prs/listComments.ts
2350
- import { execSync as execSync13 } from "child_process";
2351
2377
  import { existsSync as existsSync12, mkdirSync as mkdirSync3, writeFileSync as writeFileSync11 } from "fs";
2352
2378
  import { join as join11 } from "path";
2353
- import chalk31 from "chalk";
2379
+ import chalk32 from "chalk";
2354
2380
  import { stringify } from "yaml";
2355
2381
 
2356
2382
  // src/lib/isClaudeCode.ts
@@ -2358,8 +2384,53 @@ function isClaudeCode() {
2358
2384
  return process.env.CLAUDECODE !== void 0;
2359
2385
  }
2360
2386
 
2387
+ // src/commands/prs/fetchReviewComments.ts
2388
+ import { execSync as execSync13 } from "child_process";
2389
+ function fetchReviewComments(org, repo, prNumber) {
2390
+ const result = execSync13(
2391
+ `gh api repos/${org}/${repo}/pulls/${prNumber}/reviews`,
2392
+ { encoding: "utf-8" }
2393
+ );
2394
+ if (!result.trim()) return [];
2395
+ const reviews = JSON.parse(result);
2396
+ return reviews.filter((r) => r.body).map(
2397
+ (r) => ({
2398
+ type: "review",
2399
+ id: r.id,
2400
+ user: r.user.login,
2401
+ state: r.state,
2402
+ body: r.body
2403
+ })
2404
+ );
2405
+ }
2406
+ function fetchLineComments(org, repo, prNumber, threadInfo) {
2407
+ const result = execSync13(
2408
+ `gh api repos/${org}/${repo}/pulls/${prNumber}/comments`,
2409
+ { encoding: "utf-8" }
2410
+ );
2411
+ if (!result.trim()) return [];
2412
+ const comments = JSON.parse(result);
2413
+ return comments.map(
2414
+ (c) => {
2415
+ const threadId = threadInfo.threadMap.get(c.id) ?? "";
2416
+ return {
2417
+ type: "line",
2418
+ id: c.id,
2419
+ threadId,
2420
+ user: c.user.login,
2421
+ path: c.path,
2422
+ line: c.line,
2423
+ body: c.body,
2424
+ diff_hunk: c.diff_hunk,
2425
+ html_url: c.html_url,
2426
+ resolved: threadInfo.resolvedThreadIds.has(threadId)
2427
+ };
2428
+ }
2429
+ );
2430
+ }
2431
+
2361
2432
  // src/commands/prs/fetchThreadIds.ts
2362
- import { execSync as execSync12 } from "child_process";
2433
+ import { execSync as execSync14 } from "child_process";
2363
2434
  import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "fs";
2364
2435
  import { tmpdir as tmpdir2 } from "os";
2365
2436
  import { join as join10 } from "path";
@@ -2368,7 +2439,7 @@ function fetchThreadIds(org, repo, prNumber) {
2368
2439
  const queryFile = join10(tmpdir2(), `gh-query-${Date.now()}.graphql`);
2369
2440
  writeFileSync10(queryFile, THREAD_QUERY);
2370
2441
  try {
2371
- const result = execSync12(
2442
+ const result = execSync14(
2372
2443
  `gh api graphql -F query=@${queryFile} -F owner="${org}" -F repo="${repo}" -F prNumber=${prNumber}`,
2373
2444
  { encoding: "utf-8" }
2374
2445
  );
@@ -2392,17 +2463,17 @@ function fetchThreadIds(org, repo, prNumber) {
2392
2463
  // src/commands/prs/listComments.ts
2393
2464
  function formatForHuman(comment) {
2394
2465
  if (comment.type === "review") {
2395
- 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;
2396
2467
  return [
2397
- `${chalk31.cyan("Review")} by ${chalk31.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
2468
+ `${chalk32.cyan("Review")} by ${chalk32.bold(comment.user)} ${stateColor(`[${comment.state}]`)}`,
2398
2469
  comment.body,
2399
2470
  ""
2400
2471
  ].join("\n");
2401
2472
  }
2402
2473
  const location = comment.line ? `:${comment.line}` : "";
2403
2474
  return [
2404
- `${chalk31.cyan("Line comment")} by ${chalk31.bold(comment.user)} on ${chalk31.dim(`${comment.path}${location}`)}`,
2405
- 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")),
2406
2477
  comment.body,
2407
2478
  ""
2408
2479
  ].join("\n");
@@ -2439,48 +2510,6 @@ function writeCommentsCache(prNumber, comments) {
2439
2510
  const cachePath = join11(assistDir, `pr-${prNumber}-comments.yaml`);
2440
2511
  writeFileSync11(cachePath, stringify(cacheData));
2441
2512
  }
2442
- function fetchReviewComments(org, repo, prNumber) {
2443
- const result = execSync13(
2444
- `gh api repos/${org}/${repo}/pulls/${prNumber}/reviews`,
2445
- { encoding: "utf-8" }
2446
- );
2447
- if (!result.trim()) return [];
2448
- const reviews = JSON.parse(result);
2449
- return reviews.filter((r) => r.body).map(
2450
- (r) => ({
2451
- type: "review",
2452
- id: r.id,
2453
- user: r.user.login,
2454
- state: r.state,
2455
- body: r.body
2456
- })
2457
- );
2458
- }
2459
- function fetchLineComments(org, repo, prNumber, threadInfo) {
2460
- const result = execSync13(
2461
- `gh api repos/${org}/${repo}/pulls/${prNumber}/comments`,
2462
- { encoding: "utf-8" }
2463
- );
2464
- if (!result.trim()) return [];
2465
- const comments = JSON.parse(result);
2466
- return comments.map(
2467
- (c) => {
2468
- const threadId = threadInfo.threadMap.get(c.id) ?? "";
2469
- return {
2470
- type: "line",
2471
- id: c.id,
2472
- threadId,
2473
- user: c.user.login,
2474
- path: c.path,
2475
- line: c.line,
2476
- body: c.body,
2477
- diff_hunk: c.diff_hunk,
2478
- html_url: c.html_url,
2479
- resolved: threadInfo.resolvedThreadIds.has(threadId)
2480
- };
2481
- }
2482
- );
2483
- }
2484
2513
  async function listComments() {
2485
2514
  try {
2486
2515
  const prNumber = getCurrentPrNumber();
@@ -2511,67 +2540,45 @@ async function listComments() {
2511
2540
  }
2512
2541
 
2513
2542
  // src/commands/prs/prs.ts
2514
- import { execSync as execSync14 } from "child_process";
2515
- import chalk32 from "chalk";
2543
+ import { execSync as execSync15 } from "child_process";
2544
+
2545
+ // src/commands/prs/displayPaginated.ts
2546
+ import chalk33 from "chalk";
2516
2547
  import enquirer4 from "enquirer";
2517
2548
  var PAGE_SIZE = 10;
2518
- async function prs(options) {
2519
- const state = options.open ? "open" : options.closed ? "closed" : "all";
2520
- try {
2521
- const result = execSync14(
2522
- `gh pr list --state ${state} --json number,title,url,author,createdAt,mergedAt,closedAt,state,changedFiles --limit 100`,
2523
- { 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})`)}`
2524
2573
  );
2525
- const pullRequests = JSON.parse(result);
2526
- if (pullRequests.length === 0) {
2527
- console.log(
2528
- `No ${state === "all" ? "" : `${state} `}pull requests found.`
2529
- );
2530
- return;
2531
- }
2532
- await displayPaginated(pullRequests);
2533
- } catch (error) {
2534
- if (isGhNotInstalled(error)) {
2535
- console.error("Error: GitHub CLI (gh) is not installed.");
2536
- console.error("Install it from https://cli.github.com/");
2537
- return;
2538
- }
2539
- throw error;
2574
+ console.log(chalk33.dim(` ${fileCount} files | ${pr.url}`));
2575
+ console.log();
2540
2576
  }
2541
2577
  }
2542
2578
  async function displayPaginated(pullRequests) {
2543
2579
  const totalPages = Math.ceil(pullRequests.length / PAGE_SIZE);
2544
2580
  let currentPage = 0;
2545
- const getStatus = (pr) => {
2546
- if (pr.state === "MERGED" && pr.mergedAt) {
2547
- return { label: chalk32.magenta("merged"), date: pr.mergedAt };
2548
- }
2549
- if (pr.state === "CLOSED" && pr.closedAt) {
2550
- return { label: chalk32.red("closed"), date: pr.closedAt };
2551
- }
2552
- return { label: chalk32.green("opened"), date: pr.createdAt };
2553
- };
2554
- const displayPage = (page) => {
2555
- const start = page * PAGE_SIZE;
2556
- const end = Math.min(start + PAGE_SIZE, pullRequests.length);
2557
- const pagePrs = pullRequests.slice(start, end);
2558
- console.log(
2559
- `
2560
- Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
2561
- `
2562
- );
2563
- for (const pr of pagePrs) {
2564
- const status = getStatus(pr);
2565
- const formattedDate = new Date(status.date).toISOString().split("T")[0];
2566
- const fileCount = pr.changedFiles.toLocaleString();
2567
- console.log(
2568
- `${chalk32.cyan(`#${pr.number}`)} ${pr.title} ${chalk32.dim(`(${pr.author.login},`)} ${status.label} ${chalk32.dim(`${formattedDate})`)}`
2569
- );
2570
- console.log(chalk32.dim(` ${fileCount} files | ${pr.url}`));
2571
- console.log();
2572
- }
2573
- };
2574
- displayPage(currentPage);
2581
+ displayPage(pullRequests, totalPages, currentPage);
2575
2582
  if (totalPages <= 1) {
2576
2583
  return;
2577
2584
  }
@@ -2590,18 +2597,44 @@ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
2590
2597
  });
2591
2598
  if (action === "Next page") {
2592
2599
  currentPage++;
2593
- displayPage(currentPage);
2600
+ displayPage(pullRequests, totalPages, currentPage);
2594
2601
  } else if (action === "Previous page") {
2595
2602
  currentPage--;
2596
- displayPage(currentPage);
2603
+ displayPage(pullRequests, totalPages, currentPage);
2597
2604
  } else {
2598
2605
  break;
2599
2606
  }
2600
2607
  }
2601
2608
  }
2602
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
+
2603
2636
  // src/commands/prs/wontfix.ts
2604
- import { execSync as execSync15 } from "child_process";
2637
+ import { execSync as execSync16 } from "child_process";
2605
2638
  function validateReason(reason) {
2606
2639
  const lowerReason = reason.toLowerCase();
2607
2640
  if (lowerReason.includes("claude") || lowerReason.includes("opus")) {
@@ -2618,7 +2651,7 @@ function validateShaReferences(reason) {
2618
2651
  const invalidShas = [];
2619
2652
  for (const sha of shas) {
2620
2653
  try {
2621
- execSync15(`git cat-file -t ${sha}`, { stdio: "pipe" });
2654
+ execSync16(`git cat-file -t ${sha}`, { stdio: "pipe" });
2622
2655
  } catch {
2623
2656
  invalidShas.push(sha);
2624
2657
  }
@@ -2647,21 +2680,21 @@ function wontfix(commentId, reason) {
2647
2680
 
2648
2681
  // src/commands/refactor/check.ts
2649
2682
  import { spawn as spawn2 } from "child_process";
2650
- import * as path14 from "path";
2683
+ import * as path15 from "path";
2651
2684
 
2652
2685
  // src/commands/refactor/getViolations.ts
2653
- import { execSync as execSync16 } from "child_process";
2654
- import fs14 from "fs";
2686
+ import { execSync as execSync17 } from "child_process";
2687
+ import fs15 from "fs";
2655
2688
  import { minimatch as minimatch2 } from "minimatch";
2656
2689
 
2657
2690
  // src/commands/refactor/getIgnoredFiles.ts
2658
- import fs13 from "fs";
2691
+ import fs14 from "fs";
2659
2692
  var REFACTOR_YML_PATH = "refactor.yml";
2660
2693
  function parseRefactorYml() {
2661
- if (!fs13.existsSync(REFACTOR_YML_PATH)) {
2694
+ if (!fs14.existsSync(REFACTOR_YML_PATH)) {
2662
2695
  return [];
2663
2696
  }
2664
- const content = fs13.readFileSync(REFACTOR_YML_PATH, "utf-8");
2697
+ const content = fs14.readFileSync(REFACTOR_YML_PATH, "utf-8");
2665
2698
  const entries = [];
2666
2699
  const lines = content.split("\n");
2667
2700
  let currentEntry = {};
@@ -2690,7 +2723,7 @@ function getIgnoredFiles() {
2690
2723
  }
2691
2724
 
2692
2725
  // src/commands/refactor/logViolations.ts
2693
- import chalk33 from "chalk";
2726
+ import chalk34 from "chalk";
2694
2727
  var DEFAULT_MAX_LINES = 100;
2695
2728
  function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
2696
2729
  if (violations.length === 0) {
@@ -2699,43 +2732,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
2699
2732
  }
2700
2733
  return;
2701
2734
  }
2702
- console.error(chalk33.red(`
2735
+ console.error(chalk34.red(`
2703
2736
  Refactor check failed:
2704
2737
  `));
2705
- console.error(chalk33.red(` The following files exceed ${maxLines} lines:
2738
+ console.error(chalk34.red(` The following files exceed ${maxLines} lines:
2706
2739
  `));
2707
2740
  for (const violation of violations) {
2708
- console.error(chalk33.red(` ${violation.file} (${violation.lines} lines)`));
2741
+ console.error(chalk34.red(` ${violation.file} (${violation.lines} lines)`));
2709
2742
  }
2710
2743
  console.error(
2711
- chalk33.yellow(
2744
+ chalk34.yellow(
2712
2745
  `
2713
2746
  Each file needs to be sensibly refactored, or if there is no sensible
2714
2747
  way to refactor it, ignore it with:
2715
2748
  `
2716
2749
  )
2717
2750
  );
2718
- console.error(chalk33.gray(` assist refactor ignore <file>
2751
+ console.error(chalk34.gray(` assist refactor ignore <file>
2719
2752
  `));
2720
2753
  if (process.env.CLAUDECODE) {
2721
- console.error(chalk33.cyan(`
2754
+ console.error(chalk34.cyan(`
2722
2755
  ## Extracting Code to New Files
2723
2756
  `));
2724
2757
  console.error(
2725
- chalk33.cyan(
2758
+ chalk34.cyan(
2726
2759
  ` When extracting logic from one file to another, consider where the extracted code belongs:
2727
2760
  `
2728
2761
  )
2729
2762
  );
2730
2763
  console.error(
2731
- chalk33.cyan(
2764
+ chalk34.cyan(
2732
2765
  ` 1. Keep related logic together: If the extracted code is tightly coupled to the
2733
2766
  original file's domain, create a new folder containing both the original and extracted files.
2734
2767
  `
2735
2768
  )
2736
2769
  );
2737
2770
  console.error(
2738
- chalk33.cyan(
2771
+ chalk34.cyan(
2739
2772
  ` 2. Share common utilities: If the extracted code can be reused across multiple
2740
2773
  domains, move it to a common/shared folder.
2741
2774
  `
@@ -2746,7 +2779,7 @@ Refactor check failed:
2746
2779
 
2747
2780
  // src/commands/refactor/getViolations.ts
2748
2781
  function countLines(filePath) {
2749
- const content = fs14.readFileSync(filePath, "utf-8");
2782
+ const content = fs15.readFileSync(filePath, "utf-8");
2750
2783
  return content.split("\n").length;
2751
2784
  }
2752
2785
  function getGitFiles(options) {
@@ -2755,7 +2788,7 @@ function getGitFiles(options) {
2755
2788
  }
2756
2789
  const files = /* @__PURE__ */ new Set();
2757
2790
  if (options.staged || options.modified) {
2758
- const staged = execSync16("git diff --cached --name-only", {
2791
+ const staged = execSync17("git diff --cached --name-only", {
2759
2792
  encoding: "utf-8"
2760
2793
  });
2761
2794
  for (const file of staged.trim().split("\n").filter(Boolean)) {
@@ -2763,7 +2796,7 @@ function getGitFiles(options) {
2763
2796
  }
2764
2797
  }
2765
2798
  if (options.unstaged || options.modified) {
2766
- const unstaged = execSync16("git diff --name-only", { encoding: "utf-8" });
2799
+ const unstaged = execSync17("git diff --name-only", { encoding: "utf-8" });
2767
2800
  for (const file of unstaged.trim().split("\n").filter(Boolean)) {
2768
2801
  files.add(file);
2769
2802
  }
@@ -2771,7 +2804,7 @@ function getGitFiles(options) {
2771
2804
  return files;
2772
2805
  }
2773
2806
  function getViolations(pattern2, options = {}, maxLines = DEFAULT_MAX_LINES) {
2774
- let sourceFiles = findSourceFiles("src", { includeTests: false });
2807
+ let sourceFiles = findSourceFiles2("src", { includeTests: false });
2775
2808
  const ignoredFiles = getIgnoredFiles();
2776
2809
  const gitFiles = getGitFiles(options);
2777
2810
  if (pattern2) {
@@ -2798,7 +2831,7 @@ async function runVerifyQuietly() {
2798
2831
  return true;
2799
2832
  }
2800
2833
  const { packageJsonPath, verifyScripts } = result;
2801
- const packageDir = path14.dirname(packageJsonPath);
2834
+ const packageDir = path15.dirname(packageJsonPath);
2802
2835
  const results = await Promise.all(
2803
2836
  verifyScripts.map(
2804
2837
  (script) => new Promise(
@@ -2851,33 +2884,451 @@ async function check(pattern2, options) {
2851
2884
  }
2852
2885
 
2853
2886
  // src/commands/refactor/ignore.ts
2854
- import fs15 from "fs";
2855
- import chalk34 from "chalk";
2887
+ import fs16 from "fs";
2888
+ import chalk35 from "chalk";
2856
2889
  var REFACTOR_YML_PATH2 = "refactor.yml";
2857
2890
  function ignore(file) {
2858
- if (!fs15.existsSync(file)) {
2859
- console.error(chalk34.red(`Error: File does not exist: ${file}`));
2891
+ if (!fs16.existsSync(file)) {
2892
+ console.error(chalk35.red(`Error: File does not exist: ${file}`));
2860
2893
  process.exit(1);
2861
2894
  }
2862
- const content = fs15.readFileSync(file, "utf-8");
2895
+ const content = fs16.readFileSync(file, "utf-8");
2863
2896
  const lineCount = content.split("\n").length;
2864
2897
  const maxLines = lineCount + 10;
2865
2898
  const entry = `- file: ${file}
2866
2899
  maxLines: ${maxLines}
2867
2900
  `;
2868
- if (fs15.existsSync(REFACTOR_YML_PATH2)) {
2869
- const existing = fs15.readFileSync(REFACTOR_YML_PATH2, "utf-8");
2870
- fs15.writeFileSync(REFACTOR_YML_PATH2, existing + entry);
2901
+ if (fs16.existsSync(REFACTOR_YML_PATH2)) {
2902
+ const existing = fs16.readFileSync(REFACTOR_YML_PATH2, "utf-8");
2903
+ fs16.writeFileSync(REFACTOR_YML_PATH2, existing + entry);
2871
2904
  } else {
2872
- fs15.writeFileSync(REFACTOR_YML_PATH2, entry);
2905
+ fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
2873
2906
  }
2874
2907
  console.log(
2875
- chalk34.green(
2908
+ chalk35.green(
2876
2909
  `Added ${file} to refactor ignore list (max ${maxLines} lines)`
2877
2910
  )
2878
2911
  );
2879
2912
  }
2880
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
+
2881
3332
  // src/commands/run.ts
2882
3333
  import { spawn as spawn3 } from "child_process";
2883
3334
  function run(name, args) {
@@ -2963,29 +3414,29 @@ async function statusLine() {
2963
3414
  }
2964
3415
 
2965
3416
  // src/commands/sync.ts
2966
- import * as fs18 from "fs";
3417
+ import * as fs22 from "fs";
2967
3418
  import * as os from "os";
2968
- import * as path17 from "path";
3419
+ import * as path26 from "path";
2969
3420
  import { fileURLToPath as fileURLToPath3 } from "url";
2970
3421
 
2971
3422
  // src/commands/sync/syncClaudeMd.ts
2972
- import * as fs16 from "fs";
2973
- import * as path15 from "path";
2974
- import chalk35 from "chalk";
3423
+ import * as fs20 from "fs";
3424
+ import * as path24 from "path";
3425
+ import chalk39 from "chalk";
2975
3426
  async function syncClaudeMd(claudeDir, targetBase) {
2976
- const source = path15.join(claudeDir, "CLAUDE.md");
2977
- const target = path15.join(targetBase, "CLAUDE.md");
2978
- const sourceContent = fs16.readFileSync(source, "utf-8");
2979
- if (fs16.existsSync(target)) {
2980
- const targetContent = fs16.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");
2981
3432
  if (sourceContent !== targetContent) {
2982
3433
  console.log(
2983
- 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")
2984
3435
  );
2985
3436
  console.log();
2986
3437
  printDiff(targetContent, sourceContent);
2987
3438
  const confirm = await promptConfirm(
2988
- chalk35.red("Overwrite existing CLAUDE.md?"),
3439
+ chalk39.red("Overwrite existing CLAUDE.md?"),
2989
3440
  false
2990
3441
  );
2991
3442
  if (!confirm) {
@@ -2994,30 +3445,30 @@ async function syncClaudeMd(claudeDir, targetBase) {
2994
3445
  }
2995
3446
  }
2996
3447
  }
2997
- fs16.copyFileSync(source, target);
3448
+ fs20.copyFileSync(source, target);
2998
3449
  console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
2999
3450
  }
3000
3451
 
3001
3452
  // src/commands/sync/syncSettings.ts
3002
- import * as fs17 from "fs";
3003
- import * as path16 from "path";
3004
- import chalk36 from "chalk";
3453
+ import * as fs21 from "fs";
3454
+ import * as path25 from "path";
3455
+ import chalk40 from "chalk";
3005
3456
  async function syncSettings(claudeDir, targetBase) {
3006
- const source = path16.join(claudeDir, "settings.json");
3007
- const target = path16.join(targetBase, "settings.json");
3008
- const sourceContent = fs17.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");
3009
3460
  const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
3010
- if (fs17.existsSync(target)) {
3011
- const targetContent = fs17.readFileSync(target, "utf-8");
3461
+ if (fs21.existsSync(target)) {
3462
+ const targetContent = fs21.readFileSync(target, "utf-8");
3012
3463
  const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
3013
3464
  if (normalizedSource !== normalizedTarget) {
3014
3465
  console.log(
3015
- 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")
3016
3467
  );
3017
3468
  console.log();
3018
3469
  printDiff(targetContent, sourceContent);
3019
3470
  const confirm = await promptConfirm(
3020
- chalk36.red("Overwrite existing settings.json?"),
3471
+ chalk40.red("Overwrite existing settings.json?"),
3021
3472
  false
3022
3473
  );
3023
3474
  if (!confirm) {
@@ -3026,27 +3477,27 @@ async function syncSettings(claudeDir, targetBase) {
3026
3477
  }
3027
3478
  }
3028
3479
  }
3029
- fs17.copyFileSync(source, target);
3480
+ fs21.copyFileSync(source, target);
3030
3481
  console.log("Copied settings.json to ~/.claude/settings.json");
3031
3482
  }
3032
3483
 
3033
3484
  // src/commands/sync.ts
3034
3485
  var __filename2 = fileURLToPath3(import.meta.url);
3035
- var __dirname4 = path17.dirname(__filename2);
3486
+ var __dirname4 = path26.dirname(__filename2);
3036
3487
  async function sync() {
3037
- const claudeDir = path17.join(__dirname4, "..", "claude");
3038
- const targetBase = path17.join(os.homedir(), ".claude");
3488
+ const claudeDir = path26.join(__dirname4, "..", "claude");
3489
+ const targetBase = path26.join(os.homedir(), ".claude");
3039
3490
  syncCommands(claudeDir, targetBase);
3040
3491
  await syncSettings(claudeDir, targetBase);
3041
3492
  await syncClaudeMd(claudeDir, targetBase);
3042
3493
  }
3043
3494
  function syncCommands(claudeDir, targetBase) {
3044
- const sourceDir = path17.join(claudeDir, "commands");
3045
- const targetDir = path17.join(targetBase, "commands");
3046
- fs18.mkdirSync(targetDir, { recursive: true });
3047
- const files = fs18.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);
3048
3499
  for (const file of files) {
3049
- fs18.copyFileSync(path17.join(sourceDir, file), path17.join(targetDir, file));
3500
+ fs22.copyFileSync(path26.join(sourceDir, file), path26.join(targetDir, file));
3050
3501
  console.log(`Copied ${file} to ${targetDir}`);
3051
3502
  }
3052
3503
  console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
@@ -3168,58 +3619,28 @@ async function configure() {
3168
3619
  import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
3169
3620
  import { basename as basename4, dirname as dirname11, join as join17 } from "path";
3170
3621
 
3171
- // src/commands/transcript/parseVtt.ts
3172
- function parseTimestamp(ts4) {
3173
- const parts = ts4.split(":");
3174
- let hours = 0;
3175
- let minutes = 0;
3176
- let seconds = 0;
3177
- if (parts.length === 3) {
3178
- hours = Number.parseInt(parts[0], 10);
3179
- minutes = Number.parseInt(parts[1], 10);
3180
- seconds = Number.parseFloat(parts[2]);
3181
- } else if (parts.length === 2) {
3182
- minutes = Number.parseInt(parts[0], 10);
3183
- seconds = Number.parseFloat(parts[1]);
3184
- }
3185
- return Math.round((hours * 3600 + minutes * 60 + seconds) * 1e3);
3186
- }
3187
- function parseVtt(content) {
3188
- const cues = [];
3189
- const lines = content.split(/\r?\n/);
3190
- let i = 0;
3191
- while (i < lines.length && !lines[i].includes("-->")) {
3192
- i++;
3193
- }
3194
- while (i < lines.length) {
3195
- const line = lines[i].trim();
3196
- if (line.includes("-->")) {
3197
- const [startStr, endStr] = line.split("-->").map((s) => s.trim());
3198
- const startMs = parseTimestamp(startStr);
3199
- const endMs = parseTimestamp(endStr);
3200
- const textLines = [];
3201
- i++;
3202
- while (i < lines.length && lines[i].trim() && !lines[i].includes("-->")) {
3203
- textLines.push(lines[i].trim());
3204
- i++;
3205
- }
3206
- const fullText = textLines.join(" ");
3207
- const speakerMatch = fullText.match(/^<v\s+([^>]+)>/);
3208
- let speaker = null;
3209
- let text = fullText;
3210
- if (speakerMatch) {
3211
- speaker = speakerMatch[1];
3212
- text = fullText.replace(/<v\s+[^>]+>/, "").trim();
3213
- }
3214
- if (text) {
3215
- cues.push({ startMs, endMs, speaker, text });
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;
3216
3634
  }
3217
- } else {
3218
- i++;
3635
+ }
3636
+ if (!isRepeat) {
3637
+ cleaned.push(words[i]);
3219
3638
  }
3220
3639
  }
3221
- return cues;
3640
+ return cleaned.join(" ").replace(/\s+/g, " ").trim();
3222
3641
  }
3642
+
3643
+ // src/commands/transcript/deduplicateCues.ts
3223
3644
  function removeSubstringDuplicates(cues) {
3224
3645
  const toRemove = /* @__PURE__ */ new Set();
3225
3646
  for (let i = 0; i < cues.length; i++) {
@@ -3240,6 +3661,16 @@ function removeSubstringDuplicates(cues) {
3240
3661
  }
3241
3662
  return cues.filter((_, i) => !toRemove.has(i));
3242
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
+ }
3243
3674
  function mergeOverlappingCues(cues) {
3244
3675
  if (cues.length === 0) return [];
3245
3676
  const result = [];
@@ -3251,15 +3682,7 @@ function mergeOverlappingCues(cues) {
3251
3682
  if (sameSpeaker && overlaps) {
3252
3683
  const currentWords = current.text.toLowerCase().split(/\s+/);
3253
3684
  const nextWords = next2.text.toLowerCase().split(/\s+/);
3254
- let overlapIndex = -1;
3255
- for (let j = Math.min(5, currentWords.length); j >= 1; j--) {
3256
- const suffix = currentWords.slice(-j).join(" ");
3257
- const prefix = nextWords.slice(0, j).join(" ");
3258
- if (suffix === prefix) {
3259
- overlapIndex = j;
3260
- break;
3261
- }
3262
- }
3685
+ const overlapIndex = findWordOverlap(currentWords, nextWords);
3263
3686
  if (overlapIndex > 0) {
3264
3687
  const nextOriginalWords = next2.text.split(/\s+/);
3265
3688
  current.text = `${current.text} ${nextOriginalWords.slice(overlapIndex).join(" ")}`;
@@ -3275,25 +3698,6 @@ function mergeOverlappingCues(cues) {
3275
3698
  result.push(current);
3276
3699
  return result;
3277
3700
  }
3278
- function cleanText(text) {
3279
- const words = text.split(/\s+/);
3280
- const cleaned = [];
3281
- for (let i = 0; i < words.length; i++) {
3282
- let isRepeat = false;
3283
- for (let len = 3; len <= 8 && i + len <= words.length; len++) {
3284
- const phrase = words.slice(i, i + len).join(" ").toLowerCase();
3285
- const remaining = words.slice(i + len).join(" ").toLowerCase();
3286
- if (remaining.startsWith(phrase)) {
3287
- isRepeat = true;
3288
- break;
3289
- }
3290
- }
3291
- if (!isRepeat) {
3292
- cleaned.push(words[i]);
3293
- }
3294
- }
3295
- return cleaned.join(" ").replace(/\s+/g, " ").trim();
3296
- }
3297
3701
  function deduplicateCues(cues) {
3298
3702
  if (cues.length === 0) return [];
3299
3703
  const sorted = [...cues].sort((a, b) => a.startMs - b.startMs);
@@ -3305,6 +3709,59 @@ function deduplicateCues(cues) {
3305
3709
  }));
3306
3710
  }
3307
3711
 
3712
+ // src/commands/transcript/parseVtt.ts
3713
+ function parseTimestamp(ts6) {
3714
+ const parts = ts6.split(":");
3715
+ let hours = 0;
3716
+ let minutes = 0;
3717
+ let seconds = 0;
3718
+ if (parts.length === 3) {
3719
+ hours = Number.parseInt(parts[0], 10);
3720
+ minutes = Number.parseInt(parts[1], 10);
3721
+ seconds = Number.parseFloat(parts[2]);
3722
+ } else if (parts.length === 2) {
3723
+ minutes = Number.parseInt(parts[0], 10);
3724
+ seconds = Number.parseFloat(parts[1]);
3725
+ }
3726
+ return Math.round((hours * 3600 + minutes * 60 + seconds) * 1e3);
3727
+ }
3728
+ function parseVtt(content) {
3729
+ const cues = [];
3730
+ const lines = content.split(/\r?\n/);
3731
+ let i = 0;
3732
+ while (i < lines.length && !lines[i].includes("-->")) {
3733
+ i++;
3734
+ }
3735
+ while (i < lines.length) {
3736
+ const line = lines[i].trim();
3737
+ if (line.includes("-->")) {
3738
+ const [startStr, endStr] = line.split("-->").map((s) => s.trim());
3739
+ const startMs = parseTimestamp(startStr);
3740
+ const endMs = parseTimestamp(endStr);
3741
+ const textLines = [];
3742
+ i++;
3743
+ while (i < lines.length && lines[i].trim() && !lines[i].includes("-->")) {
3744
+ textLines.push(lines[i].trim());
3745
+ i++;
3746
+ }
3747
+ const fullText = textLines.join(" ");
3748
+ const speakerMatch = fullText.match(/^<v\s+([^>]+)>/);
3749
+ let speaker = null;
3750
+ let text = fullText;
3751
+ if (speakerMatch) {
3752
+ speaker = speakerMatch[1];
3753
+ text = fullText.replace(/<v\s+[^>]+>/, "").trim();
3754
+ }
3755
+ if (text) {
3756
+ cues.push({ startMs, endMs, speaker, text });
3757
+ }
3758
+ } else {
3759
+ i++;
3760
+ }
3761
+ }
3762
+ return cues;
3763
+ }
3764
+
3308
3765
  // src/commands/transcript/formatChatLog.ts
3309
3766
  function cuesToChatMessages(cues) {
3310
3767
  const messages = [];
@@ -3400,23 +3857,7 @@ function processFile(inputPath, outputPath) {
3400
3857
  `
3401
3858
  );
3402
3859
  }
3403
- async function format() {
3404
- const { vttDir, transcriptsDir } = getTranscriptConfig();
3405
- if (!existsSync16(vttDir)) {
3406
- console.error(`VTT directory not found: ${vttDir}`);
3407
- process.exit(1);
3408
- }
3409
- if (!existsSync16(transcriptsDir)) {
3410
- mkdirSync5(transcriptsDir, { recursive: true });
3411
- console.log(`Created output directory: ${transcriptsDir}`);
3412
- }
3413
- let vttFiles = findVttFilesRecursive(vttDir);
3414
- if (vttFiles.length === 0) {
3415
- console.log("No VTT files found in vtt directory.");
3416
- return;
3417
- }
3418
- console.log(`Found ${vttFiles.length} VTT file(s) in ${vttDir}
3419
- `);
3860
+ async function fixInvalidDatePrefixes(vttFiles) {
3420
3861
  for (let i = 0; i < vttFiles.length; i++) {
3421
3862
  const vttFile = vttFiles[i];
3422
3863
  if (!isValidDatePrefix(vttFile.filename)) {
@@ -3437,7 +3878,26 @@ async function format() {
3437
3878
  }
3438
3879
  }
3439
3880
  }
3440
- 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);
3441
3901
  let processed = 0;
3442
3902
  let skipped = 0;
3443
3903
  for (const vttFile of vttFiles) {
@@ -3464,6 +3924,10 @@ Summary: ${processed} processed, ${skipped} skipped`);
3464
3924
  }
3465
3925
 
3466
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
3467
3931
  import {
3468
3932
  existsSync as existsSync17,
3469
3933
  mkdirSync as mkdirSync6,
@@ -3471,8 +3935,8 @@ import {
3471
3935
  renameSync as renameSync2,
3472
3936
  rmSync
3473
3937
  } from "fs";
3474
- import { basename as basename5, dirname as dirname12, join as join18, relative as relative2 } from "path";
3475
- import chalk37 from "chalk";
3938
+ import { dirname as dirname12, join as join18 } from "path";
3939
+ import chalk41 from "chalk";
3476
3940
  var STAGING_DIR = join18(process.cwd(), ".assist", "transcript");
3477
3941
  var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
3478
3942
  function processStagedFile() {
@@ -3490,7 +3954,7 @@ function processStagedFile() {
3490
3954
  const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
3491
3955
  if (!match) {
3492
3956
  console.error(
3493
- chalk37.red(
3957
+ chalk41.red(
3494
3958
  `Staged file ${stagedFile.filename} missing [Full Transcript](<path>) link on first line.`
3495
3959
  )
3496
3960
  );
@@ -3499,7 +3963,7 @@ function processStagedFile() {
3499
3963
  const contentAfterLink = content.slice(firstLine.length).trim();
3500
3964
  if (!contentAfterLink) {
3501
3965
  console.error(
3502
- chalk37.red(
3966
+ chalk41.red(
3503
3967
  `Staged file ${stagedFile.filename} has no summary content after the transcript link.`
3504
3968
  )
3505
3969
  );
@@ -3512,7 +3976,7 @@ function processStagedFile() {
3512
3976
  );
3513
3977
  if (!matchingTranscript) {
3514
3978
  console.error(
3515
- chalk37.red(
3979
+ chalk41.red(
3516
3980
  `No transcript found matching staged file: ${stagedFile.filename}`
3517
3981
  )
3518
3982
  );
@@ -3531,10 +3995,12 @@ function processStagedFile() {
3531
3995
  }
3532
3996
  return true;
3533
3997
  }
3998
+
3999
+ // src/commands/transcript/summarise.ts
3534
4000
  function summarise() {
3535
4001
  processStagedFile();
3536
4002
  const { transcriptsDir, summaryDir } = getTranscriptConfig();
3537
- if (!existsSync17(transcriptsDir)) {
4003
+ if (!existsSync18(transcriptsDir)) {
3538
4004
  console.log("No transcripts directory found.");
3539
4005
  return;
3540
4006
  }
@@ -3546,16 +4012,16 @@ function summarise() {
3546
4012
  const summaryFiles = findMdFilesRecursive(summaryDir);
3547
4013
  const summaryRelativePaths = new Set(
3548
4014
  summaryFiles.map((f) => {
3549
- const relDir = dirname12(f.relativePath);
4015
+ const relDir = dirname13(f.relativePath);
3550
4016
  const baseName = basename5(f.filename, ".md");
3551
- return relDir === "." ? baseName : join18(relDir, baseName);
4017
+ return relDir === "." ? baseName : join19(relDir, baseName);
3552
4018
  })
3553
4019
  );
3554
4020
  const missing = [];
3555
4021
  for (const transcript of transcriptFiles) {
3556
4022
  const transcriptBaseName = getTranscriptBaseName(transcript.filename);
3557
- const relDir = dirname12(transcript.relativePath);
3558
- const fullKey = relDir === "." ? transcriptBaseName : join18(relDir, transcriptBaseName);
4023
+ const relDir = dirname13(transcript.relativePath);
4024
+ const fullKey = relDir === "." ? transcriptBaseName : join19(relDir, transcriptBaseName);
3559
4025
  if (!summaryRelativePaths.has(fullKey)) {
3560
4026
  missing.push(transcript);
3561
4027
  }
@@ -3566,8 +4032,8 @@ function summarise() {
3566
4032
  }
3567
4033
  const next2 = missing[0];
3568
4034
  const outputFilename = `${getTranscriptBaseName(next2.filename)}.md`;
3569
- const outputPath = join18(STAGING_DIR, outputFilename);
3570
- const summaryFileDir = join18(summaryDir, dirname12(next2.relativePath));
4035
+ const outputPath = join19(STAGING_DIR, outputFilename);
4036
+ const summaryFileDir = join19(summaryDir, dirname13(next2.relativePath));
3571
4037
  const relativeTranscriptPath = encodeURI(
3572
4038
  relative2(summaryFileDir, next2.absolutePath).replace(/\\/g, "/")
3573
4039
  );
@@ -3584,11 +4050,11 @@ function summarise() {
3584
4050
  }
3585
4051
 
3586
4052
  // src/commands/verify/hardcodedColors.ts
3587
- import { execSync as execSync17 } from "child_process";
4053
+ import { execSync as execSync18 } from "child_process";
3588
4054
  var pattern = "0x[0-9a-fA-F]{6}|#[0-9a-fA-F]{3,6}";
3589
4055
  function hardcodedColors() {
3590
4056
  try {
3591
- const output = execSync17(`grep -rEnH '${pattern}' src/`, {
4057
+ const output = execSync18(`grep -rEnH '${pattern}' src/`, {
3592
4058
  encoding: "utf-8"
3593
4059
  });
3594
4060
  const lines = output.trim().split("\n");
@@ -3618,7 +4084,7 @@ Total: ${lines.length} hardcoded color(s)`);
3618
4084
 
3619
4085
  // src/commands/verify/run.ts
3620
4086
  import { spawn as spawn4 } from "child_process";
3621
- import * as path18 from "path";
4087
+ import * as path27 from "path";
3622
4088
  function formatDuration(ms) {
3623
4089
  if (ms < 1e3) {
3624
4090
  return `${ms}ms`;
@@ -3648,7 +4114,7 @@ async function run2(options = {}) {
3648
4114
  return;
3649
4115
  }
3650
4116
  const { packageJsonPath, verifyScripts } = result;
3651
- const packageDir = path18.dirname(packageJsonPath);
4117
+ const packageDir = path27.dirname(packageJsonPath);
3652
4118
  console.log(`Running ${verifyScripts.length} verify script(s) in parallel:`);
3653
4119
  for (const script of verifyScripts) {
3654
4120
  console.log(` - ${script}`);
@@ -3698,7 +4164,7 @@ program.command("init").description("Initialize VS Code and verify configuration
3698
4164
  program.command("commit <message>").description("Create a git commit with validation").action(commit);
3699
4165
  program.command("update").description("Update claude-code to the latest version").action(() => {
3700
4166
  console.log("Updating claude-code...");
3701
- execSync18("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
4167
+ execSync19("npm install -g @anthropic-ai/claude-code", { stdio: "inherit" });
3702
4168
  });
3703
4169
  var configCommand = program.command("config").description("View and modify assist.yml configuration");
3704
4170
  configCommand.command("set <key> <value>").description("Set a config value (e.g. commit.push true)").action(configSet);
@@ -3731,6 +4197,13 @@ refactorCommand.command("check [pattern]").description("Check for files that exc
3731
4197
  Number.parseInt
3732
4198
  ).action(check);
3733
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);
3734
4207
  var devlogCommand = program.command("devlog").description("Development log utilities");
3735
4208
  devlogCommand.command("list").description("Group git commits by date").option(
3736
4209
  "--days <number>",