@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/README.md +1 -0
- package/claude/settings.json +1 -1
- package/dist/commands/deploy/init.ts +1 -54
- package/dist/commands/deploy/updateWorkflow.ts +56 -0
- package/dist/index.js +1157 -684
- package/package.json +2 -2
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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 (
|
|
334
|
+
} else if (ts2.isBinaryExpression(n) && logicalOperators.has(n.operatorToken.kind)) {
|
|
245
335
|
complexity++;
|
|
246
336
|
}
|
|
247
|
-
|
|
337
|
+
ts2.forEachChild(n, visit);
|
|
248
338
|
}
|
|
249
|
-
|
|
339
|
+
ts2.forEachChild(node, visit);
|
|
250
340
|
return complexity;
|
|
251
341
|
}
|
|
252
342
|
|
|
253
343
|
// src/commands/complexity/calculateHalstead.ts
|
|
254
|
-
import
|
|
344
|
+
import ts3 from "typescript";
|
|
255
345
|
var operatorChecks = [
|
|
256
|
-
(n) =>
|
|
257
|
-
(n) =>
|
|
258
|
-
(n) =>
|
|
259
|
-
(n) =>
|
|
260
|
-
(n) =>
|
|
261
|
-
(n) =>
|
|
262
|
-
(n) =>
|
|
263
|
-
(n) =>
|
|
264
|
-
(n) =>
|
|
265
|
-
(n) =>
|
|
266
|
-
(n) =>
|
|
267
|
-
(n) =>
|
|
268
|
-
(n) =>
|
|
269
|
-
(n) =>
|
|
270
|
-
(n) =>
|
|
271
|
-
(n) =>
|
|
272
|
-
(n) =>
|
|
273
|
-
(n) =>
|
|
274
|
-
(n) =>
|
|
275
|
-
(n) =>
|
|
276
|
-
(n) =>
|
|
277
|
-
(n) =>
|
|
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 (
|
|
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
|
-
|
|
407
|
+
ts3.forEachChild(n, visit);
|
|
318
408
|
}
|
|
319
|
-
|
|
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 =
|
|
398
|
-
return
|
|
399
|
-
|
|
452
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
453
|
+
return ts4.createSourceFile(
|
|
454
|
+
path2.basename(filePath),
|
|
400
455
|
content,
|
|
401
|
-
|
|
456
|
+
ts4.ScriptTarget.Latest,
|
|
402
457
|
true,
|
|
403
|
-
filePath.endsWith(".tsx") ?
|
|
458
|
+
filePath.endsWith(".tsx") ? ts4.ScriptKind.TSX : ts4.ScriptKind.TS
|
|
404
459
|
);
|
|
405
460
|
}
|
|
406
461
|
function withSourceFiles(pattern2, callback) {
|
|
407
|
-
const files =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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,
|
|
635
|
-
const keys =
|
|
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,
|
|
646
|
-
const keys =
|
|
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
|
|
674
|
-
console.error(chalk7.red(`${
|
|
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
|
|
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/
|
|
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(
|
|
820
|
+
console.log(chalk10.bold("Initializing Netlify deployment...\n"));
|
|
796
821
|
const existingSiteId = getExistingSiteId();
|
|
797
822
|
if (existingSiteId) {
|
|
798
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
863
|
+
console.log(chalk10.bold("\nDeployment initialized successfully!"));
|
|
839
864
|
console.log(
|
|
840
|
-
|
|
865
|
+
chalk10.yellow("\nTo complete setup, create a personal access token at:")
|
|
841
866
|
);
|
|
842
867
|
console.log(
|
|
843
|
-
|
|
868
|
+
chalk10.cyan(
|
|
844
869
|
"https://app.netlify.com/user/applications#personal-access-tokens"
|
|
845
870
|
)
|
|
846
871
|
);
|
|
847
872
|
console.log(
|
|
848
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(` ${
|
|
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(` ${
|
|
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
|
|
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(`${
|
|
1038
|
+
console.log(`${chalk13.bold.blue(date)} ${chalk13.dim("skipped")}`);
|
|
1011
1039
|
} else if (entries && entries.length > 0) {
|
|
1012
|
-
const entryInfo = entries.map((e) => `${
|
|
1013
|
-
console.log(`${
|
|
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(`${
|
|
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
|
|
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
|
|
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(
|
|
1162
|
+
console.log(chalk14.dim("No commits after last versioned entry"));
|
|
1133
1163
|
} else {
|
|
1134
|
-
console.log(
|
|
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(`${
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
|
1181
|
+
import chalk15 from "chalk";
|
|
1160
1182
|
function skip(date) {
|
|
1161
1183
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
1162
|
-
console.log(
|
|
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(
|
|
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(
|
|
1203
|
+
console.log(chalk15.green(`Added ${date} to skip list`));
|
|
1182
1204
|
}
|
|
1183
1205
|
|
|
1184
1206
|
// src/commands/devlog/version.ts
|
|
1185
|
-
import
|
|
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(`${
|
|
1193
|
-
console.log(`${
|
|
1194
|
-
console.log(`${
|
|
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
|
|
1220
|
+
import chalk27 from "chalk";
|
|
1199
1221
|
|
|
1200
1222
|
// src/shared/promptMultiselect.ts
|
|
1201
|
-
import
|
|
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} - ${
|
|
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
|
|
1225
|
-
import * as
|
|
1226
|
-
import
|
|
1227
|
-
function findPackageJson() {
|
|
1228
|
-
const packageJsonPath =
|
|
1229
|
-
if (
|
|
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(
|
|
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(
|
|
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 =
|
|
1250
|
-
if (
|
|
1251
|
-
const packageJson = JSON.parse(
|
|
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 =
|
|
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
|
|
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
|
|
1283
|
-
import * as
|
|
1284
|
-
import
|
|
1304
|
+
import * as fs6 from "fs";
|
|
1305
|
+
import * as path4 from "path";
|
|
1306
|
+
import chalk19 from "chalk";
|
|
1285
1307
|
function writePackageJson(filePath, pkg) {
|
|
1286
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
1331
|
+
const knipJsonPath = path4.join(cwd, "knip.json");
|
|
1310
1332
|
try {
|
|
1311
1333
|
let knipConfig;
|
|
1312
|
-
if (
|
|
1313
|
-
knipConfig = JSON.parse(
|
|
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
|
-
|
|
1342
|
+
fs6.writeFileSync(
|
|
1321
1343
|
knipJsonPath,
|
|
1322
1344
|
`${JSON.stringify(knipConfig, null, " ")}
|
|
1323
1345
|
`
|
|
1324
1346
|
);
|
|
1325
|
-
console.log(
|
|
1347
|
+
console.log(chalk19.dim(`Added '${binary}' to knip.json ignoreBinaries`));
|
|
1326
1348
|
}
|
|
1327
1349
|
} catch {
|
|
1328
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
|
1356
|
-
import
|
|
1377
|
+
import * as path5 from "path";
|
|
1378
|
+
import chalk21 from "chalk";
|
|
1357
1379
|
async function setupDuplicateCode(packageJsonPath) {
|
|
1358
|
-
console.log(
|
|
1359
|
-
const cwd =
|
|
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
|
|
1374
|
-
import
|
|
1395
|
+
import * as path6 from "path";
|
|
1396
|
+
import chalk22 from "chalk";
|
|
1375
1397
|
async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
|
|
1376
|
-
console.log(
|
|
1377
|
-
const cwd =
|
|
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
|
|
1391
|
-
import
|
|
1412
|
+
import * as path7 from "path";
|
|
1413
|
+
import chalk23 from "chalk";
|
|
1392
1414
|
async function setupKnip(packageJsonPath) {
|
|
1393
|
-
console.log(
|
|
1394
|
-
const cwd =
|
|
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
|
|
1408
|
-
import
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
1536
|
-
const cwd =
|
|
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
|
|
1553
|
-
import
|
|
1574
|
+
import * as path9 from "path";
|
|
1575
|
+
import chalk26 from "chalk";
|
|
1554
1576
|
async function setupTest(packageJsonPath) {
|
|
1555
|
-
console.log(
|
|
1556
|
-
const cwd =
|
|
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(
|
|
1720
|
+
console.log(chalk27.green("All verify scripts are already configured!"));
|
|
1699
1721
|
return;
|
|
1700
1722
|
}
|
|
1701
|
-
console.log(
|
|
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(
|
|
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(
|
|
1754
|
+
console.log(chalk27.green(`
|
|
1733
1755
|
Added ${selected.length} verify script(s):`));
|
|
1734
1756
|
for (const choice of selected) {
|
|
1735
|
-
console.log(
|
|
1757
|
+
console.log(chalk27.green(` - verify:${choice}`));
|
|
1736
1758
|
}
|
|
1737
|
-
console.log(
|
|
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 =
|
|
1751
|
-
if (!
|
|
1752
|
-
|
|
1753
|
-
console.log(
|
|
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 =
|
|
1758
|
-
if (!
|
|
1779
|
+
const gitignorePath = path10.join(process.cwd(), ".gitignore");
|
|
1780
|
+
if (!fs7.existsSync(gitignorePath)) {
|
|
1759
1781
|
return;
|
|
1760
1782
|
}
|
|
1761
|
-
const content =
|
|
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
|
-
|
|
1768
|
-
console.log(
|
|
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 =
|
|
1784
|
-
|
|
1805
|
+
const launchPath = path10.join(process.cwd(), ".vscode", "launch.json");
|
|
1806
|
+
fs7.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
|
|
1785
1807
|
`);
|
|
1786
|
-
console.log(
|
|
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 =
|
|
1797
|
-
|
|
1818
|
+
const settingsPath = path10.join(process.cwd(), ".vscode", "settings.json");
|
|
1819
|
+
fs7.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
|
|
1798
1820
|
`);
|
|
1799
|
-
console.log(
|
|
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 =
|
|
1806
|
-
|
|
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(
|
|
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 =
|
|
1838
|
+
const vscodeDir = path11.join(process.cwd(), ".vscode");
|
|
1817
1839
|
return {
|
|
1818
|
-
hasVscodeFolder:
|
|
1819
|
-
hasLaunchJson:
|
|
1820
|
-
hasSettingsJson:
|
|
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(
|
|
1865
|
+
console.log(chalk29.green("VS Code configuration already exists!"));
|
|
1844
1866
|
return;
|
|
1845
1867
|
}
|
|
1846
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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
|
|
1882
|
-
import
|
|
1883
|
-
import
|
|
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
|
|
1887
|
-
import
|
|
1908
|
+
import fs9 from "fs";
|
|
1909
|
+
import path12 from "path";
|
|
1888
1910
|
var EXTENSIONS = [".ts", ".tsx"];
|
|
1889
|
-
function
|
|
1911
|
+
function findSourceFiles2(dir, options = {}) {
|
|
1890
1912
|
const { includeTests = true } = options;
|
|
1891
1913
|
const results = [];
|
|
1892
|
-
if (!
|
|
1914
|
+
if (!fs9.existsSync(dir)) {
|
|
1893
1915
|
return results;
|
|
1894
1916
|
}
|
|
1895
|
-
const entries =
|
|
1917
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1896
1918
|
for (const entry of entries) {
|
|
1897
|
-
const fullPath =
|
|
1919
|
+
const fullPath = path12.join(dir, entry.name);
|
|
1898
1920
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
1899
|
-
results.push(...
|
|
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 =
|
|
1940
|
+
const sourceFiles = findSourceFiles2("src");
|
|
1919
1941
|
const violations = [];
|
|
1920
1942
|
for (const filePath of sourceFiles) {
|
|
1921
|
-
const fileName =
|
|
1943
|
+
const fileName = path13.basename(filePath);
|
|
1922
1944
|
const nameWithoutExt = fileName.replace(/\.(ts|tsx)$/, "");
|
|
1923
1945
|
if (/^[A-Z]/.test(nameWithoutExt)) {
|
|
1924
|
-
const content =
|
|
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(
|
|
1957
|
+
console.error(chalk30.red("\nFile name check failed:\n"));
|
|
1936
1958
|
console.error(
|
|
1937
|
-
|
|
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(
|
|
1964
|
+
console.error(chalk30.red(` ${violation.filePath}`));
|
|
1943
1965
|
console.error(
|
|
1944
|
-
|
|
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
|
|
1983
|
+
import fs11 from "fs";
|
|
1962
1984
|
|
|
1963
1985
|
// src/commands/lint/shared.ts
|
|
1964
|
-
import
|
|
1986
|
+
import chalk31 from "chalk";
|
|
1965
1987
|
function reportViolations(violations, checkName, errorMessage, successMessage) {
|
|
1966
1988
|
if (violations.length > 0) {
|
|
1967
|
-
console.error(
|
|
1989
|
+
console.error(chalk31.red(`
|
|
1968
1990
|
${checkName} failed:
|
|
1969
1991
|
`));
|
|
1970
|
-
console.error(
|
|
1992
|
+
console.error(chalk31.red(` ${errorMessage}
|
|
1971
1993
|
`));
|
|
1972
1994
|
for (const violation of violations) {
|
|
1973
|
-
console.error(
|
|
1974
|
-
console.error(
|
|
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 =
|
|
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 =
|
|
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
|
|
2043
|
+
import fs12 from "fs";
|
|
2022
2044
|
function checkForDynamicImports(filePath) {
|
|
2023
|
-
const content =
|
|
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 =
|
|
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
|
|
2159
|
+
import fs13 from "fs";
|
|
2138
2160
|
import { createRequire } from "module";
|
|
2139
|
-
import
|
|
2161
|
+
import path14 from "path";
|
|
2140
2162
|
var require2 = createRequire(import.meta.url);
|
|
2141
2163
|
function getSnoreToastPath() {
|
|
2142
|
-
const notifierPath =
|
|
2143
|
-
return
|
|
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
|
-
|
|
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/
|
|
2224
|
-
import { execSync as
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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" ?
|
|
2466
|
+
const stateColor = comment.state === "APPROVED" ? chalk32.green : comment.state === "CHANGES_REQUESTED" ? chalk32.red : chalk32.yellow;
|
|
2396
2467
|
return [
|
|
2397
|
-
`${
|
|
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
|
-
`${
|
|
2405
|
-
|
|
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
|
|
2515
|
-
|
|
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
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
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
|
-
|
|
2526
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2683
|
+
import * as path15 from "path";
|
|
2651
2684
|
|
|
2652
2685
|
// src/commands/refactor/getViolations.ts
|
|
2653
|
-
import { execSync as
|
|
2654
|
-
import
|
|
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
|
|
2691
|
+
import fs14 from "fs";
|
|
2659
2692
|
var REFACTOR_YML_PATH = "refactor.yml";
|
|
2660
2693
|
function parseRefactorYml() {
|
|
2661
|
-
if (!
|
|
2694
|
+
if (!fs14.existsSync(REFACTOR_YML_PATH)) {
|
|
2662
2695
|
return [];
|
|
2663
2696
|
}
|
|
2664
|
-
const content =
|
|
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
|
|
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(
|
|
2735
|
+
console.error(chalk34.red(`
|
|
2703
2736
|
Refactor check failed:
|
|
2704
2737
|
`));
|
|
2705
|
-
console.error(
|
|
2738
|
+
console.error(chalk34.red(` The following files exceed ${maxLines} lines:
|
|
2706
2739
|
`));
|
|
2707
2740
|
for (const violation of violations) {
|
|
2708
|
-
console.error(
|
|
2741
|
+
console.error(chalk34.red(` ${violation.file} (${violation.lines} lines)`));
|
|
2709
2742
|
}
|
|
2710
2743
|
console.error(
|
|
2711
|
-
|
|
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(
|
|
2751
|
+
console.error(chalk34.gray(` assist refactor ignore <file>
|
|
2719
2752
|
`));
|
|
2720
2753
|
if (process.env.CLAUDECODE) {
|
|
2721
|
-
console.error(
|
|
2754
|
+
console.error(chalk34.cyan(`
|
|
2722
2755
|
## Extracting Code to New Files
|
|
2723
2756
|
`));
|
|
2724
2757
|
console.error(
|
|
2725
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
2855
|
-
import
|
|
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 (!
|
|
2859
|
-
console.error(
|
|
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 =
|
|
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 (
|
|
2869
|
-
const existing =
|
|
2870
|
-
|
|
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
|
-
|
|
2905
|
+
fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
|
|
2873
2906
|
}
|
|
2874
2907
|
console.log(
|
|
2875
|
-
|
|
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
|
|
3417
|
+
import * as fs22 from "fs";
|
|
2967
3418
|
import * as os from "os";
|
|
2968
|
-
import * as
|
|
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
|
|
2973
|
-
import * as
|
|
2974
|
-
import
|
|
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 =
|
|
2977
|
-
const target =
|
|
2978
|
-
const sourceContent =
|
|
2979
|
-
if (
|
|
2980
|
-
const targetContent =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3003
|
-
import * as
|
|
3004
|
-
import
|
|
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 =
|
|
3007
|
-
const target =
|
|
3008
|
-
const sourceContent =
|
|
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 (
|
|
3011
|
-
const targetContent =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
3486
|
+
var __dirname4 = path26.dirname(__filename2);
|
|
3036
3487
|
async function sync() {
|
|
3037
|
-
const claudeDir =
|
|
3038
|
-
const targetBase =
|
|
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 =
|
|
3045
|
-
const targetDir =
|
|
3046
|
-
|
|
3047
|
-
const files =
|
|
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
|
-
|
|
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/
|
|
3172
|
-
function
|
|
3173
|
-
const
|
|
3174
|
-
|
|
3175
|
-
let
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
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
|
-
}
|
|
3218
|
-
|
|
3635
|
+
}
|
|
3636
|
+
if (!isRepeat) {
|
|
3637
|
+
cleaned.push(words[i]);
|
|
3219
3638
|
}
|
|
3220
3639
|
}
|
|
3221
|
-
return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 {
|
|
3475
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
4015
|
+
const relDir = dirname13(f.relativePath);
|
|
3550
4016
|
const baseName = basename5(f.filename, ".md");
|
|
3551
|
-
return 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 =
|
|
3558
|
-
const fullKey = 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 =
|
|
3570
|
-
const summaryFileDir =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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>",
|