@staff0rd/assist 0.96.0 → 0.97.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 +3 -2
- package/assist.cli-reads +44 -0
- package/claude/settings.json +0 -1
- package/dist/index.js +1370 -1226
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.97.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -44,6 +44,7 @@ var package_default = {
|
|
|
44
44
|
minimatch: "^10.1.1",
|
|
45
45
|
"node-notifier": "^10.0.1",
|
|
46
46
|
semver: "^7.7.3",
|
|
47
|
+
"shell-quote": "^1.8.3",
|
|
47
48
|
typescript: "^5.9.3",
|
|
48
49
|
yaml: "^2.8.2",
|
|
49
50
|
zod: "^4.3.6"
|
|
@@ -59,6 +60,7 @@ var package_default = {
|
|
|
59
60
|
"@types/react": "^19.2.14",
|
|
60
61
|
"@types/react-dom": "^19.2.3",
|
|
61
62
|
"@types/semver": "^7.7.1",
|
|
63
|
+
"@types/shell-quote": "^1.7.5",
|
|
62
64
|
esbuild: "^0.27.3",
|
|
63
65
|
jotai: "^2.18.0",
|
|
64
66
|
jscpd: "^4.0.5",
|
|
@@ -1625,14 +1627,14 @@ function flushIfFailed(exitCode, chunks) {
|
|
|
1625
1627
|
|
|
1626
1628
|
// src/commands/verify/run/runAllEntries.ts
|
|
1627
1629
|
function runEntry(entry, onComplete) {
|
|
1628
|
-
return new Promise((
|
|
1630
|
+
return new Promise((resolve6) => {
|
|
1629
1631
|
const child = spawnCommand(entry.fullCommand, entry.cwd, entry.env);
|
|
1630
1632
|
const chunks = collectOutput(child);
|
|
1631
1633
|
child.on("close", (code) => {
|
|
1632
1634
|
const exitCode = code ?? 1;
|
|
1633
1635
|
flushIfFailed(exitCode, chunks);
|
|
1634
1636
|
onComplete?.(exitCode);
|
|
1635
|
-
|
|
1637
|
+
resolve6({ script: entry.name, code: exitCode });
|
|
1636
1638
|
});
|
|
1637
1639
|
});
|
|
1638
1640
|
}
|
|
@@ -2375,12 +2377,12 @@ function respondJson(res, status2, data) {
|
|
|
2375
2377
|
res.end(JSON.stringify(data));
|
|
2376
2378
|
}
|
|
2377
2379
|
function readBody(req) {
|
|
2378
|
-
return new Promise((
|
|
2380
|
+
return new Promise((resolve6, reject) => {
|
|
2379
2381
|
let body = "";
|
|
2380
2382
|
req.on("data", (chunk) => {
|
|
2381
2383
|
body += chunk.toString();
|
|
2382
2384
|
});
|
|
2383
|
-
req.on("end", () =>
|
|
2385
|
+
req.on("end", () => resolve6(body));
|
|
2384
2386
|
req.on("error", reject);
|
|
2385
2387
|
});
|
|
2386
2388
|
}
|
|
@@ -2517,6 +2519,9 @@ function registerBacklog(program2) {
|
|
|
2517
2519
|
backlogCommand.command("web").description("Start a web view of the backlog").option("-p, --port <number>", "Port to listen on", "3000").action(web);
|
|
2518
2520
|
}
|
|
2519
2521
|
|
|
2522
|
+
// src/shared/isApprovedRead.ts
|
|
2523
|
+
import { resolve as resolve3 } from "path";
|
|
2524
|
+
|
|
2520
2525
|
// src/shared/tokenize.ts
|
|
2521
2526
|
function tokenize(command) {
|
|
2522
2527
|
const tokens = [];
|
|
@@ -2632,6 +2637,128 @@ function findCliRead(command) {
|
|
|
2632
2637
|
return candidates.sort((a, b) => b.length - a.length).find((rc) => command === rc || command.startsWith(`${rc} `));
|
|
2633
2638
|
}
|
|
2634
2639
|
|
|
2640
|
+
// src/shared/matchesBashAllow.ts
|
|
2641
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
2642
|
+
import { homedir as homedir3 } from "os";
|
|
2643
|
+
import { join as join11 } from "path";
|
|
2644
|
+
var cached;
|
|
2645
|
+
function loadBashAllowPrefixes() {
|
|
2646
|
+
if (cached) return cached;
|
|
2647
|
+
cached = parsePrefixes(collectAllowEntries());
|
|
2648
|
+
return cached;
|
|
2649
|
+
}
|
|
2650
|
+
function matchesBashAllow(command) {
|
|
2651
|
+
const prefixes = loadBashAllowPrefixes();
|
|
2652
|
+
return prefixes.find(
|
|
2653
|
+
(pfx) => command === pfx || command.startsWith(`${pfx} `)
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
function collectAllowEntries() {
|
|
2657
|
+
const paths = [
|
|
2658
|
+
join11(homedir3(), ".claude", "settings.json"),
|
|
2659
|
+
join11(process.cwd(), ".claude", "settings.json"),
|
|
2660
|
+
join11(process.cwd(), ".claude", "settings.local.json")
|
|
2661
|
+
];
|
|
2662
|
+
const entries = [];
|
|
2663
|
+
for (const p of paths) {
|
|
2664
|
+
entries.push(...readAllowArray(p));
|
|
2665
|
+
}
|
|
2666
|
+
return entries;
|
|
2667
|
+
}
|
|
2668
|
+
function readAllowArray(filePath) {
|
|
2669
|
+
if (!existsSync17(filePath)) return [];
|
|
2670
|
+
try {
|
|
2671
|
+
const data = JSON.parse(readFileSync14(filePath, "utf-8"));
|
|
2672
|
+
const allow = data?.permissions?.allow;
|
|
2673
|
+
return Array.isArray(allow) ? allow.filter((e) => typeof e === "string") : [];
|
|
2674
|
+
} catch {
|
|
2675
|
+
return [];
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
function parsePrefixes(entries) {
|
|
2679
|
+
const re = /^Bash\((.+?)(?::.*)\)$/;
|
|
2680
|
+
const prefixes = [];
|
|
2681
|
+
for (const entry of entries) {
|
|
2682
|
+
const m = entry.match(re);
|
|
2683
|
+
if (m) prefixes.push(m[1]);
|
|
2684
|
+
}
|
|
2685
|
+
return prefixes;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/shared/isApprovedRead.ts
|
|
2689
|
+
function isApprovedRead(command) {
|
|
2690
|
+
if (isCdToCwd(command)) return "cd to current directory";
|
|
2691
|
+
const matched = findCliRead(command);
|
|
2692
|
+
if (matched) return `Read-only CLI command: ${matched}`;
|
|
2693
|
+
if (isGhApiRead(command)) return "Read-only gh api command";
|
|
2694
|
+
const allowMatch = matchesBashAllow(command);
|
|
2695
|
+
if (allowMatch) return `Allowed by settings: ${allowMatch}`;
|
|
2696
|
+
return void 0;
|
|
2697
|
+
}
|
|
2698
|
+
function isCdToCwd(command) {
|
|
2699
|
+
const parts = command.split(/\s+/);
|
|
2700
|
+
if (parts[0] !== "cd" || parts.length > 2) return false;
|
|
2701
|
+
if (parts.length === 1) return false;
|
|
2702
|
+
const resolved = resolve3(normalizeMsysPath(parts[1]));
|
|
2703
|
+
return resolved === resolve3(process.cwd());
|
|
2704
|
+
}
|
|
2705
|
+
function normalizeMsysPath(p) {
|
|
2706
|
+
const m = p.match(/^\/([a-zA-Z])(\/.*)/);
|
|
2707
|
+
return m ? `${m[1].toUpperCase()}:${m[2]}` : p;
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// src/shared/splitCompound.ts
|
|
2711
|
+
import { parse } from "shell-quote";
|
|
2712
|
+
var SEPARATOR_OPS = /* @__PURE__ */ new Set(["|", "&&", "||", ";"]);
|
|
2713
|
+
var UNSAFE_OPS = /* @__PURE__ */ new Set(["(", ")", ">", ">>", "<", "<&", "|&", ">&"]);
|
|
2714
|
+
var FD_REDIRECT_RE = /\d+>&\d+/g;
|
|
2715
|
+
function splitCompound(command) {
|
|
2716
|
+
const tokens = tokenizeCommand(command);
|
|
2717
|
+
if (!tokens) return void 0;
|
|
2718
|
+
const groups = groupByOperator(tokens);
|
|
2719
|
+
if (!groups) return void 0;
|
|
2720
|
+
const result = groups.map((parts) => stripEnvPrefix(parts).join(" ")).filter((cmd) => cmd !== "");
|
|
2721
|
+
return result.length > 0 ? result : void 0;
|
|
2722
|
+
}
|
|
2723
|
+
function tokenizeCommand(command) {
|
|
2724
|
+
const trimmed = command.trim().replace(FD_REDIRECT_RE, "");
|
|
2725
|
+
if (!trimmed) return void 0;
|
|
2726
|
+
try {
|
|
2727
|
+
const tokens = parse(trimmed);
|
|
2728
|
+
const hasBacktick = tokens.some(
|
|
2729
|
+
(t) => typeof t === "string" && /`.+`/.test(t)
|
|
2730
|
+
);
|
|
2731
|
+
return hasBacktick ? void 0 : tokens;
|
|
2732
|
+
} catch {
|
|
2733
|
+
return void 0;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
function getOp(token) {
|
|
2737
|
+
return typeof token === "object" && token !== null && "op" in token ? token.op : void 0;
|
|
2738
|
+
}
|
|
2739
|
+
function groupByOperator(tokens) {
|
|
2740
|
+
const groups = [[]];
|
|
2741
|
+
for (const token of tokens) {
|
|
2742
|
+
const op = getOp(token);
|
|
2743
|
+
if (op === void 0) {
|
|
2744
|
+
if (typeof token !== "string") return void 0;
|
|
2745
|
+
groups[groups.length - 1].push(token);
|
|
2746
|
+
} else if (SEPARATOR_OPS.has(op)) {
|
|
2747
|
+
groups.push([]);
|
|
2748
|
+
} else if (UNSAFE_OPS.has(op)) {
|
|
2749
|
+
return void 0;
|
|
2750
|
+
} else {
|
|
2751
|
+
return void 0;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
return groups;
|
|
2755
|
+
}
|
|
2756
|
+
function stripEnvPrefix(parts) {
|
|
2757
|
+
let i = 0;
|
|
2758
|
+
while (i < parts.length && /^[A-Za-z_]\w*=/.test(parts[i])) i++;
|
|
2759
|
+
return i > 0 ? parts.slice(i) : parts;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2635
2762
|
// src/commands/cliHook/index.ts
|
|
2636
2763
|
async function cliHook() {
|
|
2637
2764
|
const inputData = await readStdin();
|
|
@@ -2645,1291 +2772,1309 @@ async function cliHook() {
|
|
|
2645
2772
|
return;
|
|
2646
2773
|
}
|
|
2647
2774
|
const command = data.tool_input.command.trim();
|
|
2648
|
-
const
|
|
2649
|
-
if (
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2775
|
+
const parts = splitCompound(command);
|
|
2776
|
+
if (!parts) return;
|
|
2777
|
+
const reasons = [];
|
|
2778
|
+
for (const part of parts) {
|
|
2779
|
+
const reason = isApprovedRead(part);
|
|
2780
|
+
if (!reason) return;
|
|
2781
|
+
reasons.push(reason);
|
|
2782
|
+
}
|
|
2783
|
+
console.log(
|
|
2784
|
+
JSON.stringify({
|
|
2785
|
+
hookSpecificOutput: {
|
|
2786
|
+
hookEventName: "PreToolUse",
|
|
2787
|
+
permissionDecision: "allow",
|
|
2788
|
+
permissionDecisionReason: reasons.join("; ")
|
|
2789
|
+
}
|
|
2790
|
+
})
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
// src/commands/cliHook/cliHookCheck.ts
|
|
2795
|
+
function cliHookCheck(command) {
|
|
2796
|
+
const trimmed = command.trim();
|
|
2797
|
+
const parts = splitCompound(trimmed);
|
|
2798
|
+
if (!parts) {
|
|
2799
|
+
console.log("not approved (unable to parse)");
|
|
2800
|
+
process.exitCode = 1;
|
|
2659
2801
|
return;
|
|
2660
2802
|
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
);
|
|
2803
|
+
const reasons = [];
|
|
2804
|
+
for (const part of parts) {
|
|
2805
|
+
const reason = isApprovedRead(part);
|
|
2806
|
+
if (!reason) {
|
|
2807
|
+
console.log(`not approved (unrecognised: ${part})`);
|
|
2808
|
+
process.exitCode = 1;
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
reasons.push(` ${part} -> ${reason}`);
|
|
2671
2812
|
}
|
|
2813
|
+
console.log(`approved
|
|
2814
|
+
${reasons.join("\n")}`);
|
|
2672
2815
|
}
|
|
2673
2816
|
|
|
2674
|
-
// src/commands/
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
});
|
|
2679
|
-
}
|
|
2817
|
+
// src/commands/permitCliReads/index.ts
|
|
2818
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
2819
|
+
import { homedir as homedir4 } from "os";
|
|
2820
|
+
import { join as join12 } from "path";
|
|
2680
2821
|
|
|
2681
|
-
// src/
|
|
2682
|
-
import
|
|
2822
|
+
// src/shared/getInstallDir.ts
|
|
2823
|
+
import { execSync as execSync13 } from "child_process";
|
|
2824
|
+
import { dirname as dirname13, resolve as resolve4 } from "path";
|
|
2825
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
2826
|
+
var __filename3 = fileURLToPath5(import.meta.url);
|
|
2827
|
+
var __dirname6 = dirname13(__filename3);
|
|
2828
|
+
function getInstallDir() {
|
|
2829
|
+
return resolve4(__dirname6, "..");
|
|
2830
|
+
}
|
|
2831
|
+
function isGitRepo(dir) {
|
|
2832
|
+
try {
|
|
2833
|
+
const result = execSync13("git rev-parse --show-toplevel", {
|
|
2834
|
+
cwd: dir,
|
|
2835
|
+
stdio: "pipe"
|
|
2836
|
+
}).toString().trim();
|
|
2837
|
+
return resolve4(result) === resolve4(dir);
|
|
2838
|
+
} catch {
|
|
2839
|
+
return false;
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2683
2842
|
|
|
2684
|
-
// src/commands/
|
|
2685
|
-
import
|
|
2843
|
+
// src/commands/permitCliReads/assertCliExists.ts
|
|
2844
|
+
import { execSync as execSync14 } from "child_process";
|
|
2845
|
+
function assertCliExists(cli) {
|
|
2846
|
+
const binary = cli.split(/\s+/)[0];
|
|
2847
|
+
const opts = {
|
|
2848
|
+
encoding: "utf-8",
|
|
2849
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2850
|
+
};
|
|
2851
|
+
try {
|
|
2852
|
+
execSync14(`command -v ${binary}`, opts);
|
|
2853
|
+
} catch {
|
|
2854
|
+
try {
|
|
2855
|
+
execSync14(`where ${binary}`, opts);
|
|
2856
|
+
} catch {
|
|
2857
|
+
console.error(`CLI "${cli}" not found in PATH`);
|
|
2858
|
+
process.exit(1);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2686
2862
|
|
|
2687
|
-
// src/commands/
|
|
2688
|
-
import fs11 from "fs";
|
|
2689
|
-
import path16 from "path";
|
|
2863
|
+
// src/commands/permitCliReads/colorize.ts
|
|
2690
2864
|
import chalk30 from "chalk";
|
|
2691
|
-
|
|
2865
|
+
function colorize(plainOutput) {
|
|
2866
|
+
return plainOutput.split("\n").map((line) => {
|
|
2867
|
+
if (line.startsWith(" R ")) return chalk30.green(line);
|
|
2868
|
+
if (line.startsWith(" W ")) return chalk30.red(line);
|
|
2869
|
+
return line;
|
|
2870
|
+
}).join("\n");
|
|
2871
|
+
}
|
|
2692
2872
|
|
|
2693
|
-
// src/
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
import { minimatch as minimatch3 } from "minimatch";
|
|
2697
|
-
function applyIgnoreGlobs(files) {
|
|
2698
|
-
const { complexity } = loadConfig();
|
|
2699
|
-
return files.filter(
|
|
2700
|
-
(f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
|
|
2701
|
-
);
|
|
2873
|
+
// src/lib/isClaudeCode.ts
|
|
2874
|
+
function isClaudeCode() {
|
|
2875
|
+
return process.env.CLAUDECODE !== void 0;
|
|
2702
2876
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
2713
|
-
walk(fullPath, results);
|
|
2714
|
-
}
|
|
2715
|
-
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
2716
|
-
results.push(fullPath);
|
|
2877
|
+
|
|
2878
|
+
// src/commands/permitCliReads/mapAsync.ts
|
|
2879
|
+
async function mapAsync(items, concurrency, fn) {
|
|
2880
|
+
const results = new Array(items.length);
|
|
2881
|
+
let next2 = 0;
|
|
2882
|
+
async function worker() {
|
|
2883
|
+
while (next2 < items.length) {
|
|
2884
|
+
const idx = next2++;
|
|
2885
|
+
results[idx] = await fn(items[idx]);
|
|
2717
2886
|
}
|
|
2718
2887
|
}
|
|
2888
|
+
const workers = Array.from(
|
|
2889
|
+
{ length: Math.min(concurrency, items.length) },
|
|
2890
|
+
() => worker()
|
|
2891
|
+
);
|
|
2892
|
+
await Promise.all(workers);
|
|
2893
|
+
return results;
|
|
2719
2894
|
}
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2895
|
+
|
|
2896
|
+
// src/commands/permitCliReads/parseCommands.ts
|
|
2897
|
+
var COMMAND_SECTION_RE = /^((?:core |general |available |additional |other |management |targeted |alias |github actions )?(?:commands|subgroups)):?$/i;
|
|
2898
|
+
function isSkippable(name) {
|
|
2899
|
+
return name.startsWith("-") || name.startsWith("<") || name.startsWith("[");
|
|
2900
|
+
}
|
|
2901
|
+
function parseCommandLine(trimmed) {
|
|
2902
|
+
const azMatch = trimmed.match(/^(\S+)\s+(?:\[.*?]\s+)?:\s*(.+)/);
|
|
2903
|
+
if (azMatch && !isSkippable(azMatch[1])) {
|
|
2904
|
+
return { name: azMatch[1], description: azMatch[2].trim() };
|
|
2725
2905
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2906
|
+
const colonMatch = trimmed.match(/^(\S+?):\s{2,}(.+)/);
|
|
2907
|
+
if (colonMatch && !isSkippable(colonMatch[1])) {
|
|
2908
|
+
return { name: colonMatch[1], description: colonMatch[2].trim() };
|
|
2728
2909
|
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
return
|
|
2910
|
+
const spaceMatch = trimmed.match(/^(\S+)(?:,\s*\S+)?\s{2,}(.+)/);
|
|
2911
|
+
if (spaceMatch && !isSkippable(spaceMatch[1])) {
|
|
2912
|
+
return { name: spaceMatch[1], description: spaceMatch[2].trim() };
|
|
2732
2913
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
// src/commands/complexity/shared/getNodeName.ts
|
|
2738
|
-
import ts from "typescript";
|
|
2739
|
-
var FUNCTION_TYPE_CHECKS = [
|
|
2740
|
-
ts.isFunctionDeclaration,
|
|
2741
|
-
ts.isFunctionExpression,
|
|
2742
|
-
ts.isArrowFunction,
|
|
2743
|
-
ts.isMethodDeclaration,
|
|
2744
|
-
ts.isGetAccessor,
|
|
2745
|
-
ts.isSetAccessor,
|
|
2746
|
-
ts.isConstructorDeclaration
|
|
2747
|
-
];
|
|
2748
|
-
function getIdentifierText(name) {
|
|
2749
|
-
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
2750
|
-
return "<computed>";
|
|
2751
|
-
}
|
|
2752
|
-
function getArrowFunctionName(node) {
|
|
2753
|
-
const { parent } = node;
|
|
2754
|
-
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name))
|
|
2755
|
-
return parent.name.text;
|
|
2756
|
-
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name))
|
|
2757
|
-
return parent.name.text;
|
|
2758
|
-
return "<arrow>";
|
|
2914
|
+
if (/^\S+$/.test(trimmed) && !isSkippable(trimmed)) {
|
|
2915
|
+
return { name: trimmed, description: "" };
|
|
2916
|
+
}
|
|
2917
|
+
return void 0;
|
|
2759
2918
|
}
|
|
2760
|
-
function
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2919
|
+
function parseCommands(helpText) {
|
|
2920
|
+
const commands = [];
|
|
2921
|
+
let inCommandSection = false;
|
|
2922
|
+
for (const line of helpText.split("\n")) {
|
|
2923
|
+
const trimmed = line.trim();
|
|
2924
|
+
if (COMMAND_SECTION_RE.test(trimmed)) {
|
|
2925
|
+
inCommandSection = true;
|
|
2926
|
+
continue;
|
|
2927
|
+
}
|
|
2928
|
+
if (inCommandSection && trimmed && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
2929
|
+
inCommandSection = false;
|
|
2930
|
+
continue;
|
|
2931
|
+
}
|
|
2932
|
+
if (!inCommandSection || !trimmed) continue;
|
|
2933
|
+
if (trimmed.startsWith("-") || trimmed.startsWith("=")) continue;
|
|
2934
|
+
const parsed = parseCommandLine(trimmed);
|
|
2935
|
+
if (parsed) commands.push(parsed);
|
|
2769
2936
|
}
|
|
2770
|
-
|
|
2771
|
-
return "<unknown>";
|
|
2937
|
+
return commands;
|
|
2772
2938
|
}
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2939
|
+
var COMMAND_SECTION_MULTILINE_RE = new RegExp(
|
|
2940
|
+
COMMAND_SECTION_RE.source,
|
|
2941
|
+
"im"
|
|
2942
|
+
);
|
|
2943
|
+
function hasSubcommands(helpText) {
|
|
2944
|
+
return COMMAND_SECTION_MULTILINE_RE.test(helpText);
|
|
2776
2945
|
}
|
|
2777
2946
|
|
|
2778
|
-
// src/commands/
|
|
2779
|
-
import
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
ts2.SyntaxKind.CaseClause,
|
|
2788
|
-
ts2.SyntaxKind.CatchClause,
|
|
2789
|
-
ts2.SyntaxKind.ConditionalExpression
|
|
2790
|
-
]);
|
|
2791
|
-
var logicalOperators = /* @__PURE__ */ new Set([
|
|
2792
|
-
ts2.SyntaxKind.AmpersandAmpersandToken,
|
|
2793
|
-
ts2.SyntaxKind.BarBarToken,
|
|
2794
|
-
ts2.SyntaxKind.QuestionQuestionToken
|
|
2795
|
-
]);
|
|
2796
|
-
function calculateCyclomaticComplexity(node) {
|
|
2797
|
-
let complexity = 1;
|
|
2798
|
-
function visit(n) {
|
|
2799
|
-
if (complexityKinds.has(n.kind)) {
|
|
2800
|
-
complexity++;
|
|
2801
|
-
} else if (ts2.isBinaryExpression(n) && logicalOperators.has(n.operatorToken.kind)) {
|
|
2802
|
-
complexity++;
|
|
2803
|
-
}
|
|
2804
|
-
ts2.forEachChild(n, visit);
|
|
2805
|
-
}
|
|
2806
|
-
ts2.forEachChild(node, visit);
|
|
2807
|
-
return complexity;
|
|
2808
|
-
}
|
|
2809
|
-
|
|
2810
|
-
// src/commands/complexity/shared/calculateHalstead/index.ts
|
|
2811
|
-
import ts4 from "typescript";
|
|
2812
|
-
|
|
2813
|
-
// src/commands/complexity/shared/calculateHalstead/operatorChecks.ts
|
|
2814
|
-
import ts3 from "typescript";
|
|
2815
|
-
var operatorChecks = [
|
|
2816
|
-
(n) => ts3.isBinaryExpression(n) ? n.operatorToken.getText() : void 0,
|
|
2817
|
-
(n) => ts3.isPrefixUnaryExpression(n) || ts3.isPostfixUnaryExpression(n) ? ts3.tokenToString(n.operator) ?? "" : void 0,
|
|
2818
|
-
(n) => ts3.isCallExpression(n) ? "()" : void 0,
|
|
2819
|
-
(n) => ts3.isPropertyAccessExpression(n) ? "." : void 0,
|
|
2820
|
-
(n) => ts3.isElementAccessExpression(n) ? "[]" : void 0,
|
|
2821
|
-
(n) => ts3.isConditionalExpression(n) ? "?:" : void 0,
|
|
2822
|
-
(n) => ts3.isReturnStatement(n) ? "return" : void 0,
|
|
2823
|
-
(n) => ts3.isIfStatement(n) ? "if" : void 0,
|
|
2824
|
-
(n) => ts3.isForStatement(n) || ts3.isForInStatement(n) || ts3.isForOfStatement(n) ? "for" : void 0,
|
|
2825
|
-
(n) => ts3.isWhileStatement(n) ? "while" : void 0,
|
|
2826
|
-
(n) => ts3.isDoStatement(n) ? "do" : void 0,
|
|
2827
|
-
(n) => ts3.isSwitchStatement(n) ? "switch" : void 0,
|
|
2828
|
-
(n) => ts3.isCaseClause(n) ? "case" : void 0,
|
|
2829
|
-
(n) => ts3.isDefaultClause(n) ? "default" : void 0,
|
|
2830
|
-
(n) => ts3.isBreakStatement(n) ? "break" : void 0,
|
|
2831
|
-
(n) => ts3.isContinueStatement(n) ? "continue" : void 0,
|
|
2832
|
-
(n) => ts3.isThrowStatement(n) ? "throw" : void 0,
|
|
2833
|
-
(n) => ts3.isTryStatement(n) ? "try" : void 0,
|
|
2834
|
-
(n) => ts3.isCatchClause(n) ? "catch" : void 0,
|
|
2835
|
-
(n) => ts3.isNewExpression(n) ? "new" : void 0,
|
|
2836
|
-
(n) => ts3.isTypeOfExpression(n) ? "typeof" : void 0,
|
|
2837
|
-
(n) => ts3.isAwaitExpression(n) ? "await" : void 0
|
|
2838
|
-
];
|
|
2839
|
-
|
|
2840
|
-
// src/commands/complexity/shared/calculateHalstead/index.ts
|
|
2841
|
-
function classifyNode(n, operators, operands) {
|
|
2842
|
-
if (ts4.isIdentifier(n) || ts4.isNumericLiteral(n) || ts4.isStringLiteral(n)) {
|
|
2843
|
-
operands.set(n.text, (operands.get(n.text) ?? 0) + 1);
|
|
2844
|
-
return;
|
|
2845
|
-
}
|
|
2846
|
-
for (const check2 of operatorChecks) {
|
|
2847
|
-
const op = check2(n);
|
|
2848
|
-
if (op !== void 0) {
|
|
2849
|
-
operators.set(op, (operators.get(op) ?? 0) + 1);
|
|
2850
|
-
return;
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
function computeHalsteadMetrics(operators, operands) {
|
|
2855
|
-
const n1 = operators.size;
|
|
2856
|
-
const n2 = operands.size;
|
|
2857
|
-
const N1 = Array.from(operators.values()).reduce((a, b) => a + b, 0);
|
|
2858
|
-
const N2 = Array.from(operands.values()).reduce((a, b) => a + b, 0);
|
|
2859
|
-
const vocabulary = n1 + n2;
|
|
2860
|
-
const length = N1 + N2;
|
|
2861
|
-
const volume = length > 0 && vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
|
|
2862
|
-
const difficulty = n2 > 0 ? n1 / 2 * (N2 / n2) : 0;
|
|
2863
|
-
const effort = volume * difficulty;
|
|
2864
|
-
return {
|
|
2865
|
-
length,
|
|
2866
|
-
vocabulary,
|
|
2867
|
-
volume,
|
|
2868
|
-
difficulty,
|
|
2869
|
-
effort,
|
|
2870
|
-
time: effort / 18,
|
|
2871
|
-
bugsDelivered: volume / 3e3
|
|
2872
|
-
};
|
|
2873
|
-
}
|
|
2874
|
-
function calculateHalstead(node) {
|
|
2875
|
-
const operators = /* @__PURE__ */ new Map();
|
|
2876
|
-
const operands = /* @__PURE__ */ new Map();
|
|
2877
|
-
function visit(n) {
|
|
2878
|
-
classifyNode(n, operators, operands);
|
|
2879
|
-
ts4.forEachChild(n, visit);
|
|
2880
|
-
}
|
|
2881
|
-
ts4.forEachChild(node, visit);
|
|
2882
|
-
return computeHalsteadMetrics(operators, operands);
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
// src/commands/complexity/shared/countSloc.ts
|
|
2886
|
-
function countSloc(content) {
|
|
2887
|
-
let inMultiLineComment = false;
|
|
2888
|
-
let count = 0;
|
|
2889
|
-
for (const line of content.split("\n")) {
|
|
2890
|
-
const trimmed = line.trim();
|
|
2891
|
-
if (inMultiLineComment) {
|
|
2892
|
-
if (trimmed.includes("*/")) {
|
|
2893
|
-
inMultiLineComment = false;
|
|
2894
|
-
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
2895
|
-
if (afterComment.trim().length > 0) {
|
|
2896
|
-
count++;
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
continue;
|
|
2900
|
-
}
|
|
2901
|
-
if (trimmed.startsWith("//")) {
|
|
2902
|
-
continue;
|
|
2903
|
-
}
|
|
2904
|
-
if (trimmed.startsWith("/*")) {
|
|
2905
|
-
if (trimmed.includes("*/")) {
|
|
2906
|
-
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
2907
|
-
if (afterComment.trim().length > 0) {
|
|
2908
|
-
count++;
|
|
2909
|
-
}
|
|
2910
|
-
} else {
|
|
2911
|
-
inMultiLineComment = true;
|
|
2947
|
+
// src/commands/permitCliReads/runHelp.ts
|
|
2948
|
+
import { exec as exec2 } from "child_process";
|
|
2949
|
+
function runHelp(args) {
|
|
2950
|
+
return new Promise((resolve6) => {
|
|
2951
|
+
exec2(
|
|
2952
|
+
`${args.join(" ")} --help`,
|
|
2953
|
+
{ encoding: "utf-8", timeout: 3e4 },
|
|
2954
|
+
(_err, stdout, stderr) => {
|
|
2955
|
+
resolve6(stdout || stderr || "");
|
|
2912
2956
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
if (trimmed.length > 0) {
|
|
2916
|
-
count++;
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
return count;
|
|
2957
|
+
);
|
|
2958
|
+
});
|
|
2920
2959
|
}
|
|
2921
2960
|
|
|
2922
|
-
// src/commands/
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
filePath.endsWith(".tsx") ? ts5.ScriptKind.TSX : ts5.ScriptKind.TS
|
|
2931
|
-
);
|
|
2932
|
-
}
|
|
2933
|
-
function withSourceFiles(pattern2, callback) {
|
|
2934
|
-
const files = findSourceFiles2(pattern2);
|
|
2935
|
-
if (files.length === 0) {
|
|
2936
|
-
console.log(chalk30.yellow("No files found matching pattern"));
|
|
2937
|
-
return void 0;
|
|
2938
|
-
}
|
|
2939
|
-
return callback(files);
|
|
2961
|
+
// src/commands/permitCliReads/discoverAll.ts
|
|
2962
|
+
var SAFETY_DEPTH = 10;
|
|
2963
|
+
var CONCURRENCY = 8;
|
|
2964
|
+
var interactive = !isClaudeCode();
|
|
2965
|
+
function showProgress(p, label2) {
|
|
2966
|
+
if (!interactive) return;
|
|
2967
|
+
const pct = Math.round(p.done / p.total * 100);
|
|
2968
|
+
process.stderr.write(`\r\x1B[K[${pct}%] Scanning ${label2}...`);
|
|
2940
2969
|
}
|
|
2941
|
-
function
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
callback(file, getNodeName(node), node);
|
|
2947
|
-
}
|
|
2948
|
-
ts5.forEachChild(node, visit);
|
|
2949
|
-
};
|
|
2950
|
-
visit(sourceFile);
|
|
2970
|
+
async function resolveCommand(cli, path31, description, depth, p) {
|
|
2971
|
+
showProgress(p, path31.join(" "));
|
|
2972
|
+
const subHelp = await runHelp([cli, ...path31]);
|
|
2973
|
+
if (!subHelp || !hasSubcommands(subHelp)) {
|
|
2974
|
+
return [{ path: path31, description }];
|
|
2951
2975
|
}
|
|
2976
|
+
const children = await discoverAt(cli, path31, depth + 1, p);
|
|
2977
|
+
return children.length > 0 ? children : [{ path: path31, description }];
|
|
2952
2978
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
}
|
|
2965
|
-
});
|
|
2966
|
-
results.sort((a, b) => b.complexity - a.complexity);
|
|
2967
|
-
for (const { file, name, complexity } of results) {
|
|
2968
|
-
const exceedsThreshold = options2.threshold !== void 0 && complexity > options2.threshold;
|
|
2969
|
-
const color = exceedsThreshold ? chalk31.red : chalk31.white;
|
|
2970
|
-
console.log(`${color(`${file}:${name}`)} \u2192 ${chalk31.cyan(complexity)}`);
|
|
2971
|
-
}
|
|
2972
|
-
console.log(
|
|
2973
|
-
chalk31.dim(
|
|
2974
|
-
`
|
|
2975
|
-
Analyzed ${results.length} functions across ${files.length} files`
|
|
2976
|
-
)
|
|
2977
|
-
);
|
|
2978
|
-
if (hasViolation) {
|
|
2979
|
-
process.exit(1);
|
|
2980
|
-
}
|
|
2981
|
-
});
|
|
2979
|
+
async function discoverAt(cli, parentPath, depth, p) {
|
|
2980
|
+
if (depth > SAFETY_DEPTH) return [];
|
|
2981
|
+
const helpText = await runHelp([cli, ...parentPath]);
|
|
2982
|
+
if (!helpText) return [];
|
|
2983
|
+
const cmds = parseCommands(helpText);
|
|
2984
|
+
const results = await mapAsync(
|
|
2985
|
+
cmds,
|
|
2986
|
+
CONCURRENCY,
|
|
2987
|
+
(cmd) => resolveCommand(cli, [...parentPath, cmd.name], cmd.description, depth, p)
|
|
2988
|
+
);
|
|
2989
|
+
return results.flat();
|
|
2982
2990
|
}
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
const
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
hasViolation = true;
|
|
2995
|
-
}
|
|
2996
|
-
});
|
|
2997
|
-
results.sort((a, b) => b.metrics.effort - a.metrics.effort);
|
|
2998
|
-
for (const { file, name, metrics } of results) {
|
|
2999
|
-
const exceedsThreshold = options2.threshold !== void 0 && metrics.volume > options2.threshold;
|
|
3000
|
-
const color = exceedsThreshold ? chalk32.red : chalk32.white;
|
|
3001
|
-
console.log(
|
|
3002
|
-
`${color(`${file}:${name}`)} \u2192 volume: ${chalk32.cyan(metrics.volume.toFixed(1))}, difficulty: ${chalk32.yellow(metrics.difficulty.toFixed(1))}, effort: ${chalk32.magenta(metrics.effort.toFixed(1))}`
|
|
3003
|
-
);
|
|
3004
|
-
}
|
|
3005
|
-
console.log(
|
|
3006
|
-
chalk32.dim(
|
|
3007
|
-
`
|
|
3008
|
-
Analyzed ${results.length} functions across ${files.length} files`
|
|
3009
|
-
)
|
|
2991
|
+
async function discoverAll(cli) {
|
|
2992
|
+
const topLevel = parseCommands(await runHelp([cli]));
|
|
2993
|
+
const p = { done: 0, total: topLevel.length };
|
|
2994
|
+
const results = await mapAsync(topLevel, CONCURRENCY, async (cmd) => {
|
|
2995
|
+
showProgress(p, cmd.name);
|
|
2996
|
+
const resolved = await resolveCommand(
|
|
2997
|
+
cli,
|
|
2998
|
+
[cmd.name],
|
|
2999
|
+
cmd.description,
|
|
3000
|
+
1,
|
|
3001
|
+
p
|
|
3010
3002
|
);
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3003
|
+
p.done++;
|
|
3004
|
+
showProgress(p, cmd.name);
|
|
3005
|
+
return resolved;
|
|
3014
3006
|
});
|
|
3007
|
+
if (interactive) process.stderr.write("\r\x1B[K");
|
|
3008
|
+
return results.flat();
|
|
3015
3009
|
}
|
|
3016
3010
|
|
|
3017
|
-
// src/commands/
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3011
|
+
// src/commands/permitCliReads/classifyVerb.ts
|
|
3012
|
+
var READ_VERBS = /* @__PURE__ */ new Set([
|
|
3013
|
+
"list",
|
|
3014
|
+
"show",
|
|
3015
|
+
"view",
|
|
3016
|
+
"export",
|
|
3017
|
+
"get",
|
|
3018
|
+
"diff",
|
|
3019
|
+
"status",
|
|
3020
|
+
"search",
|
|
3021
|
+
"checks",
|
|
3022
|
+
"describe",
|
|
3023
|
+
"inspect",
|
|
3024
|
+
"logs",
|
|
3025
|
+
"cat",
|
|
3026
|
+
"top",
|
|
3027
|
+
"explain",
|
|
3028
|
+
"exists",
|
|
3029
|
+
"browse",
|
|
3030
|
+
"watch"
|
|
3031
|
+
]);
|
|
3032
|
+
var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
3033
|
+
"create",
|
|
3034
|
+
"delete",
|
|
3035
|
+
"import",
|
|
3036
|
+
"set",
|
|
3037
|
+
"update",
|
|
3038
|
+
"merge",
|
|
3039
|
+
"close",
|
|
3040
|
+
"reopen",
|
|
3041
|
+
"edit",
|
|
3042
|
+
"apply",
|
|
3043
|
+
"patch",
|
|
3044
|
+
"drain",
|
|
3045
|
+
"cordon",
|
|
3046
|
+
"taint",
|
|
3047
|
+
"push",
|
|
3048
|
+
"deploy",
|
|
3049
|
+
"add",
|
|
3050
|
+
"remove",
|
|
3051
|
+
"assign",
|
|
3052
|
+
"unassign",
|
|
3053
|
+
"lock",
|
|
3054
|
+
"unlock",
|
|
3055
|
+
"start",
|
|
3056
|
+
"stop",
|
|
3057
|
+
"restart",
|
|
3058
|
+
"enable",
|
|
3059
|
+
"disable",
|
|
3060
|
+
"revoke",
|
|
3061
|
+
"rotate"
|
|
3062
|
+
]);
|
|
3063
|
+
function classifyVerb(verbOrPath) {
|
|
3064
|
+
const segments = Array.isArray(verbOrPath) ? verbOrPath : [verbOrPath];
|
|
3065
|
+
let hasRead = false;
|
|
3066
|
+
for (const s of segments) {
|
|
3067
|
+
if (WRITE_VERBS.has(s)) return "w";
|
|
3068
|
+
if (READ_VERBS.has(s)) hasRead = true;
|
|
3046
3069
|
}
|
|
3070
|
+
return hasRead ? "r" : "?";
|
|
3047
3071
|
}
|
|
3048
3072
|
|
|
3049
|
-
// src/commands/
|
|
3050
|
-
function
|
|
3051
|
-
if (
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
const mi = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(sloc2);
|
|
3055
|
-
return Math.max(0, Math.min(100, mi));
|
|
3056
|
-
}
|
|
3057
|
-
function collectFileMetrics(files) {
|
|
3058
|
-
const fileMetrics = /* @__PURE__ */ new Map();
|
|
3059
|
-
for (const file of files) {
|
|
3060
|
-
const content = fs12.readFileSync(file, "utf-8");
|
|
3061
|
-
fileMetrics.set(file, { sloc: countSloc(content), functions: [] });
|
|
3062
|
-
}
|
|
3063
|
-
forEachFunction(files, (file, _name, node) => {
|
|
3064
|
-
const metrics = fileMetrics.get(file);
|
|
3065
|
-
if (metrics) {
|
|
3066
|
-
const complexity = calculateCyclomaticComplexity(node);
|
|
3067
|
-
const halstead2 = calculateHalstead(node);
|
|
3068
|
-
const mi = calculateMaintainabilityIndex(
|
|
3069
|
-
halstead2.volume,
|
|
3070
|
-
complexity,
|
|
3071
|
-
metrics.sloc
|
|
3072
|
-
);
|
|
3073
|
-
metrics.functions.push(mi);
|
|
3074
|
-
}
|
|
3075
|
-
});
|
|
3076
|
-
return fileMetrics;
|
|
3073
|
+
// src/commands/permitCliReads/formatHuman.ts
|
|
3074
|
+
function prefix(kind) {
|
|
3075
|
+
if (kind === "r") return " R ";
|
|
3076
|
+
if (kind === "w") return " W ";
|
|
3077
|
+
return " ? ";
|
|
3077
3078
|
}
|
|
3078
|
-
function
|
|
3079
|
-
const
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3079
|
+
function formatHuman(cli, commands) {
|
|
3080
|
+
const sorted = [...commands].sort(
|
|
3081
|
+
(a, b) => a.path.join(" ").localeCompare(b.path.join(" "))
|
|
3082
|
+
);
|
|
3083
|
+
const lines = [`Discovered ${commands.length} commands for "${cli}":
|
|
3084
|
+
`];
|
|
3085
|
+
for (const cmd of sorted) {
|
|
3086
|
+
const full = `${cli} ${cmd.path.join(" ")}`;
|
|
3087
|
+
const text = cmd.description ? `${full} \u2014 ${cmd.description}` : full;
|
|
3088
|
+
lines.push(`${prefix(classifyVerb(cmd.path))}${text}`);
|
|
3085
3089
|
}
|
|
3086
|
-
|
|
3087
|
-
return results;
|
|
3088
|
-
}
|
|
3089
|
-
async function maintainability(pattern2 = "**/*.ts", options2 = {}) {
|
|
3090
|
-
withSourceFiles(pattern2, (files) => {
|
|
3091
|
-
const fileMetrics = collectFileMetrics(files);
|
|
3092
|
-
const results = aggregateResults(fileMetrics);
|
|
3093
|
-
displayMaintainabilityResults(results, options2.threshold);
|
|
3094
|
-
});
|
|
3090
|
+
return lines.join("\n");
|
|
3095
3091
|
}
|
|
3096
3092
|
|
|
3097
|
-
// src/commands/
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
const
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
}
|
|
3112
|
-
results.sort((a, b) => b.lines - a.lines);
|
|
3113
|
-
for (const { file, lines } of results) {
|
|
3114
|
-
const exceedsThreshold = options2.threshold !== void 0 && lines > options2.threshold;
|
|
3115
|
-
const color = exceedsThreshold ? chalk34.red : chalk34.white;
|
|
3116
|
-
console.log(`${color(file)} \u2192 ${chalk34.cyan(lines)} lines`);
|
|
3117
|
-
}
|
|
3118
|
-
const total = results.reduce((sum, r) => sum + r.lines, 0);
|
|
3119
|
-
console.log(
|
|
3120
|
-
chalk34.dim(`
|
|
3121
|
-
Total: ${total} lines across ${files.length} files`)
|
|
3122
|
-
);
|
|
3123
|
-
if (hasViolation) {
|
|
3124
|
-
process.exit(1);
|
|
3125
|
-
}
|
|
3126
|
-
});
|
|
3093
|
+
// src/commands/permitCliReads/parseCached.ts
|
|
3094
|
+
function parseCached(cli, cached2) {
|
|
3095
|
+
const prefix2 = `${cli} `;
|
|
3096
|
+
const commands = [];
|
|
3097
|
+
for (const line of cached2.split("\n")) {
|
|
3098
|
+
const trimmed = line.replace(/^ [RW?] {2}/, "").trim();
|
|
3099
|
+
if (!trimmed.startsWith(prefix2)) continue;
|
|
3100
|
+
const rest = trimmed.slice(prefix2.length);
|
|
3101
|
+
const dashIdx = rest.indexOf(" \u2014 ");
|
|
3102
|
+
const pathStr = dashIdx >= 0 ? rest.slice(0, dashIdx) : rest;
|
|
3103
|
+
const description = dashIdx >= 0 ? rest.slice(dashIdx + 3) : "";
|
|
3104
|
+
commands.push({ path: pathStr.split(" "), description });
|
|
3105
|
+
}
|
|
3106
|
+
return commands;
|
|
3127
3107
|
}
|
|
3128
3108
|
|
|
3129
|
-
// src/commands/
|
|
3130
|
-
|
|
3131
|
-
const
|
|
3132
|
-
const
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
return;
|
|
3136
|
-
}
|
|
3137
|
-
if (files.length === 1) {
|
|
3138
|
-
const file = files[0];
|
|
3139
|
-
console.log(chalk35.bold.underline("SLOC"));
|
|
3140
|
-
await sloc(file);
|
|
3141
|
-
console.log();
|
|
3142
|
-
console.log(chalk35.bold.underline("Cyclomatic Complexity"));
|
|
3143
|
-
await cyclomatic(file);
|
|
3144
|
-
console.log();
|
|
3145
|
-
console.log(chalk35.bold.underline("Halstead Metrics"));
|
|
3146
|
-
await halstead(file);
|
|
3147
|
-
console.log();
|
|
3148
|
-
console.log(chalk35.bold.underline("Maintainability Index"));
|
|
3149
|
-
await maintainability(file);
|
|
3109
|
+
// src/commands/permitCliReads/updateSettings.ts
|
|
3110
|
+
function updateSettings(cli, commands) {
|
|
3111
|
+
const existing = loadCliReads();
|
|
3112
|
+
const readEntries = commands.filter((cmd) => classifyVerb(cmd.path) === "r").map((cmd) => `${cli} ${cmd.path.join(" ")}`);
|
|
3113
|
+
const merged = [.../* @__PURE__ */ new Set([...existing, ...readEntries])].sort();
|
|
3114
|
+
if (merged.length === existing.length && merged.every((e, i) => e === existing[i]))
|
|
3150
3115
|
return;
|
|
3151
|
-
|
|
3152
|
-
await maintainability(searchPattern);
|
|
3116
|
+
saveCliReads(merged);
|
|
3153
3117
|
}
|
|
3154
3118
|
|
|
3155
|
-
// src/commands/
|
|
3156
|
-
function
|
|
3157
|
-
const
|
|
3158
|
-
|
|
3159
|
-
complexityCommand.help();
|
|
3160
|
-
return;
|
|
3161
|
-
}
|
|
3162
|
-
return analyze(pattern2);
|
|
3163
|
-
});
|
|
3164
|
-
complexityCommand.command("cyclomatic [pattern]").description("Calculate cyclomatic complexity per function").option("--threshold <number>", "Max complexity threshold", Number.parseInt).action(cyclomatic);
|
|
3165
|
-
complexityCommand.command("halstead [pattern]").description("Calculate Halstead metrics per function").option("--threshold <number>", "Max volume threshold", Number.parseInt).action(halstead);
|
|
3166
|
-
complexityCommand.command("maintainability [pattern]").description("Calculate maintainability index per file").option(
|
|
3167
|
-
"--threshold <number>",
|
|
3168
|
-
"Min maintainability threshold",
|
|
3169
|
-
Number.parseInt
|
|
3170
|
-
).action(maintainability);
|
|
3171
|
-
complexityCommand.command("sloc [pattern]").description("Count source lines of code per file").option("--threshold <number>", "Max lines threshold", Number.parseInt).action(sloc);
|
|
3119
|
+
// src/commands/permitCliReads/index.ts
|
|
3120
|
+
function logPath(cli) {
|
|
3121
|
+
const safeName = cli.replace(/\s+/g, "-");
|
|
3122
|
+
return join12(homedir4(), ".assist", `cli-discover-${safeName}.log`);
|
|
3172
3123
|
}
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
if (!
|
|
3185
|
-
console.
|
|
3186
|
-
|
|
3124
|
+
function readCache(cli) {
|
|
3125
|
+
const path31 = logPath(cli);
|
|
3126
|
+
if (!existsSync18(path31)) return void 0;
|
|
3127
|
+
return readFileSync15(path31, "utf-8");
|
|
3128
|
+
}
|
|
3129
|
+
function writeCache(cli, output) {
|
|
3130
|
+
const dir = join12(homedir4(), ".assist");
|
|
3131
|
+
mkdirSync4(dir, { recursive: true });
|
|
3132
|
+
writeFileSync13(logPath(cli), output);
|
|
3133
|
+
}
|
|
3134
|
+
async function permitCliReads(cli, options2 = { noCache: false }) {
|
|
3135
|
+
if (!cli) {
|
|
3136
|
+
console.error("Usage: assist cli-hook add <cli>");
|
|
3137
|
+
process.exit(1);
|
|
3187
3138
|
}
|
|
3188
|
-
const
|
|
3189
|
-
if (
|
|
3190
|
-
console.
|
|
3191
|
-
|
|
3139
|
+
const installDir = getInstallDir();
|
|
3140
|
+
if (!isGitRepo(installDir)) {
|
|
3141
|
+
console.error(
|
|
3142
|
+
"cli-hook add must be run from the assist git repo, not a global install."
|
|
3143
|
+
);
|
|
3144
|
+
process.exit(1);
|
|
3192
3145
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3146
|
+
if (!options2.noCache) {
|
|
3147
|
+
const cached2 = readCache(cli);
|
|
3148
|
+
if (cached2) {
|
|
3149
|
+
console.log(colorize(cached2));
|
|
3150
|
+
updateSettings(cli, parseCached(cli, cached2));
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3197
3153
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3154
|
+
assertCliExists(cli);
|
|
3155
|
+
const commands = await discoverAll(cli);
|
|
3156
|
+
const output = formatHuman(cli, commands);
|
|
3157
|
+
console.log(colorize(output));
|
|
3158
|
+
writeCache(cli, output);
|
|
3159
|
+
updateSettings(cli, commands);
|
|
3201
3160
|
}
|
|
3202
3161
|
|
|
3203
|
-
// src/commands/
|
|
3204
|
-
function
|
|
3205
|
-
const
|
|
3206
|
-
|
|
3207
|
-
|
|
3162
|
+
// src/commands/registerCliHook.ts
|
|
3163
|
+
function registerCliHook(program2) {
|
|
3164
|
+
const cmd = program2.command("cli-hook").description("PreToolUse hook for auto-approving read-only CLI commands").action(() => {
|
|
3165
|
+
cliHook();
|
|
3166
|
+
});
|
|
3167
|
+
cmd.command("check <command>").description("Check whether a command would be auto-approved").action((command) => {
|
|
3168
|
+
cliHookCheck(command);
|
|
3169
|
+
});
|
|
3170
|
+
cmd.command("add").description("Discover a CLI's commands and auto-permit read-only ones").argument(
|
|
3171
|
+
"<cli...>",
|
|
3172
|
+
"CLI binary and optional subcommand (e.g. gh, az, acli jira)"
|
|
3173
|
+
).option("--no-cache", "Force fresh discovery, ignoring cached results").action((cli, options2) => {
|
|
3174
|
+
permitCliReads(cli.join(" "), { noCache: !options2.cache });
|
|
3175
|
+
});
|
|
3208
3176
|
}
|
|
3209
3177
|
|
|
3210
|
-
// src/commands/
|
|
3211
|
-
import
|
|
3212
|
-
import { basename as basename3 } from "path";
|
|
3178
|
+
// src/commands/complexity/analyze.ts
|
|
3179
|
+
import chalk36 from "chalk";
|
|
3213
3180
|
|
|
3214
|
-
// src/commands/
|
|
3215
|
-
import
|
|
3216
|
-
import chalk37 from "chalk";
|
|
3181
|
+
// src/commands/complexity/cyclomatic.ts
|
|
3182
|
+
import chalk32 from "chalk";
|
|
3217
3183
|
|
|
3218
|
-
// src/commands/
|
|
3219
|
-
import
|
|
3220
|
-
import
|
|
3221
|
-
import
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
const existing = entries.get(date) || [];
|
|
3246
|
-
existing.push({ version: version2, title, filename: file });
|
|
3247
|
-
entries.set(date, existing);
|
|
3248
|
-
}
|
|
3184
|
+
// src/commands/complexity/shared/index.ts
|
|
3185
|
+
import fs11 from "fs";
|
|
3186
|
+
import path16 from "path";
|
|
3187
|
+
import chalk31 from "chalk";
|
|
3188
|
+
import ts5 from "typescript";
|
|
3189
|
+
|
|
3190
|
+
// src/commands/complexity/findSourceFiles.ts
|
|
3191
|
+
import fs10 from "fs";
|
|
3192
|
+
import path15 from "path";
|
|
3193
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
3194
|
+
function applyIgnoreGlobs(files) {
|
|
3195
|
+
const { complexity } = loadConfig();
|
|
3196
|
+
return files.filter(
|
|
3197
|
+
(f) => !complexity.ignore.some((glob) => minimatch3(f, glob))
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
function walk(dir, results) {
|
|
3201
|
+
if (!fs10.existsSync(dir)) {
|
|
3202
|
+
return;
|
|
3203
|
+
}
|
|
3204
|
+
const extensions = [".ts", ".tsx"];
|
|
3205
|
+
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
3206
|
+
for (const entry of entries) {
|
|
3207
|
+
const fullPath = path15.join(dir, entry.name);
|
|
3208
|
+
if (entry.isDirectory()) {
|
|
3209
|
+
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
3210
|
+
walk(fullPath, results);
|
|
3249
3211
|
}
|
|
3212
|
+
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
3213
|
+
results.push(fullPath);
|
|
3250
3214
|
}
|
|
3251
|
-
} catch {
|
|
3252
3215
|
}
|
|
3253
|
-
return entries;
|
|
3254
3216
|
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
return
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3217
|
+
function findSourceFiles2(pattern2, baseDir = ".") {
|
|
3218
|
+
const results = [];
|
|
3219
|
+
if (pattern2.includes("*")) {
|
|
3220
|
+
walk(baseDir, results);
|
|
3221
|
+
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
3222
|
+
}
|
|
3223
|
+
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isFile()) {
|
|
3224
|
+
return [pattern2];
|
|
3225
|
+
}
|
|
3226
|
+
if (fs10.existsSync(pattern2) && fs10.statSync(pattern2).isDirectory()) {
|
|
3227
|
+
walk(pattern2, results);
|
|
3228
|
+
return applyIgnoreGlobs(results);
|
|
3265
3229
|
}
|
|
3230
|
+
walk(baseDir, results);
|
|
3231
|
+
return applyIgnoreGlobs(results.filter((f) => minimatch3(f, pattern2)));
|
|
3266
3232
|
}
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3233
|
+
|
|
3234
|
+
// src/commands/complexity/shared/getNodeName.ts
|
|
3235
|
+
import ts from "typescript";
|
|
3236
|
+
var FUNCTION_TYPE_CHECKS = [
|
|
3237
|
+
ts.isFunctionDeclaration,
|
|
3238
|
+
ts.isFunctionExpression,
|
|
3239
|
+
ts.isArrowFunction,
|
|
3240
|
+
ts.isMethodDeclaration,
|
|
3241
|
+
ts.isGetAccessor,
|
|
3242
|
+
ts.isSetAccessor,
|
|
3243
|
+
ts.isConstructorDeclaration
|
|
3244
|
+
];
|
|
3245
|
+
function getIdentifierText(name) {
|
|
3246
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
3247
|
+
return "<computed>";
|
|
3248
|
+
}
|
|
3249
|
+
function getArrowFunctionName(node) {
|
|
3250
|
+
const { parent } = node;
|
|
3251
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name))
|
|
3252
|
+
return parent.name.text;
|
|
3253
|
+
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name))
|
|
3254
|
+
return parent.name.text;
|
|
3255
|
+
return "<arrow>";
|
|
3256
|
+
}
|
|
3257
|
+
function getNodeName(node) {
|
|
3258
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node))
|
|
3259
|
+
return node.name?.text ?? "<anonymous>";
|
|
3260
|
+
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node))
|
|
3261
|
+
return getIdentifierText(node.name);
|
|
3262
|
+
if (ts.isArrowFunction(node)) return getArrowFunctionName(node);
|
|
3263
|
+
if (ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
|
|
3264
|
+
const prefix2 = ts.isGetAccessor(node) ? "get " : "set ";
|
|
3265
|
+
return `${prefix2}${getIdentifierText(node.name)}`;
|
|
3270
3266
|
}
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
);
|
|
3267
|
+
if (ts.isConstructorDeclaration(node)) return "constructor";
|
|
3268
|
+
return "<unknown>";
|
|
3274
3269
|
}
|
|
3275
|
-
function
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3270
|
+
function hasFunctionBody(node) {
|
|
3271
|
+
if (!FUNCTION_TYPE_CHECKS.some((check2) => check2(node))) return false;
|
|
3272
|
+
return node.body !== void 0;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
// src/commands/complexity/shared/calculateCyclomaticComplexity.ts
|
|
3276
|
+
import ts2 from "typescript";
|
|
3277
|
+
var complexityKinds = /* @__PURE__ */ new Set([
|
|
3278
|
+
ts2.SyntaxKind.IfStatement,
|
|
3279
|
+
ts2.SyntaxKind.ForStatement,
|
|
3280
|
+
ts2.SyntaxKind.ForInStatement,
|
|
3281
|
+
ts2.SyntaxKind.ForOfStatement,
|
|
3282
|
+
ts2.SyntaxKind.WhileStatement,
|
|
3283
|
+
ts2.SyntaxKind.DoStatement,
|
|
3284
|
+
ts2.SyntaxKind.CaseClause,
|
|
3285
|
+
ts2.SyntaxKind.CatchClause,
|
|
3286
|
+
ts2.SyntaxKind.ConditionalExpression
|
|
3287
|
+
]);
|
|
3288
|
+
var logicalOperators = /* @__PURE__ */ new Set([
|
|
3289
|
+
ts2.SyntaxKind.AmpersandAmpersandToken,
|
|
3290
|
+
ts2.SyntaxKind.BarBarToken,
|
|
3291
|
+
ts2.SyntaxKind.QuestionQuestionToken
|
|
3292
|
+
]);
|
|
3293
|
+
function calculateCyclomaticComplexity(node) {
|
|
3294
|
+
let complexity = 1;
|
|
3295
|
+
function visit(n) {
|
|
3296
|
+
if (complexityKinds.has(n.kind)) {
|
|
3297
|
+
complexity++;
|
|
3298
|
+
} else if (ts2.isBinaryExpression(n) && logicalOperators.has(n.operatorToken.kind)) {
|
|
3299
|
+
complexity++;
|
|
3285
3300
|
}
|
|
3301
|
+
ts2.forEachChild(n, visit);
|
|
3286
3302
|
}
|
|
3303
|
+
ts2.forEachChild(node, visit);
|
|
3304
|
+
return complexity;
|
|
3287
3305
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3306
|
+
|
|
3307
|
+
// src/commands/complexity/shared/calculateHalstead/index.ts
|
|
3308
|
+
import ts4 from "typescript";
|
|
3309
|
+
|
|
3310
|
+
// src/commands/complexity/shared/calculateHalstead/operatorChecks.ts
|
|
3311
|
+
import ts3 from "typescript";
|
|
3312
|
+
var operatorChecks = [
|
|
3313
|
+
(n) => ts3.isBinaryExpression(n) ? n.operatorToken.getText() : void 0,
|
|
3314
|
+
(n) => ts3.isPrefixUnaryExpression(n) || ts3.isPostfixUnaryExpression(n) ? ts3.tokenToString(n.operator) ?? "" : void 0,
|
|
3315
|
+
(n) => ts3.isCallExpression(n) ? "()" : void 0,
|
|
3316
|
+
(n) => ts3.isPropertyAccessExpression(n) ? "." : void 0,
|
|
3317
|
+
(n) => ts3.isElementAccessExpression(n) ? "[]" : void 0,
|
|
3318
|
+
(n) => ts3.isConditionalExpression(n) ? "?:" : void 0,
|
|
3319
|
+
(n) => ts3.isReturnStatement(n) ? "return" : void 0,
|
|
3320
|
+
(n) => ts3.isIfStatement(n) ? "if" : void 0,
|
|
3321
|
+
(n) => ts3.isForStatement(n) || ts3.isForInStatement(n) || ts3.isForOfStatement(n) ? "for" : void 0,
|
|
3322
|
+
(n) => ts3.isWhileStatement(n) ? "while" : void 0,
|
|
3323
|
+
(n) => ts3.isDoStatement(n) ? "do" : void 0,
|
|
3324
|
+
(n) => ts3.isSwitchStatement(n) ? "switch" : void 0,
|
|
3325
|
+
(n) => ts3.isCaseClause(n) ? "case" : void 0,
|
|
3326
|
+
(n) => ts3.isDefaultClause(n) ? "default" : void 0,
|
|
3327
|
+
(n) => ts3.isBreakStatement(n) ? "break" : void 0,
|
|
3328
|
+
(n) => ts3.isContinueStatement(n) ? "continue" : void 0,
|
|
3329
|
+
(n) => ts3.isThrowStatement(n) ? "throw" : void 0,
|
|
3330
|
+
(n) => ts3.isTryStatement(n) ? "try" : void 0,
|
|
3331
|
+
(n) => ts3.isCatchClause(n) ? "catch" : void 0,
|
|
3332
|
+
(n) => ts3.isNewExpression(n) ? "new" : void 0,
|
|
3333
|
+
(n) => ts3.isTypeOfExpression(n) ? "typeof" : void 0,
|
|
3334
|
+
(n) => ts3.isAwaitExpression(n) ? "await" : void 0
|
|
3335
|
+
];
|
|
3336
|
+
|
|
3337
|
+
// src/commands/complexity/shared/calculateHalstead/index.ts
|
|
3338
|
+
function classifyNode(n, operators, operands) {
|
|
3339
|
+
if (ts4.isIdentifier(n) || ts4.isNumericLiteral(n) || ts4.isStringLiteral(n)) {
|
|
3340
|
+
operands.set(n.text, (operands.get(n.text) ?? 0) + 1);
|
|
3341
|
+
return;
|
|
3342
|
+
}
|
|
3343
|
+
for (const check2 of operatorChecks) {
|
|
3344
|
+
const op = check2(n);
|
|
3345
|
+
if (op !== void 0) {
|
|
3346
|
+
operators.set(op, (operators.get(op) ?? 0) + 1);
|
|
3347
|
+
return;
|
|
3302
3348
|
}
|
|
3303
3349
|
}
|
|
3304
|
-
return commitsByDate;
|
|
3305
3350
|
}
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3351
|
+
function computeHalsteadMetrics(operators, operands) {
|
|
3352
|
+
const n1 = operators.size;
|
|
3353
|
+
const n2 = operands.size;
|
|
3354
|
+
const N1 = Array.from(operators.values()).reduce((a, b) => a + b, 0);
|
|
3355
|
+
const N2 = Array.from(operands.values()).reduce((a, b) => a + b, 0);
|
|
3356
|
+
const vocabulary = n1 + n2;
|
|
3357
|
+
const length = N1 + N2;
|
|
3358
|
+
const volume = length > 0 && vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
|
|
3359
|
+
const difficulty = n2 > 0 ? n1 / 2 * (N2 / n2) : 0;
|
|
3360
|
+
const effort = volume * difficulty;
|
|
3361
|
+
return {
|
|
3362
|
+
length,
|
|
3363
|
+
vocabulary,
|
|
3364
|
+
volume,
|
|
3365
|
+
difficulty,
|
|
3366
|
+
effort,
|
|
3367
|
+
time: effort / 18,
|
|
3368
|
+
bugsDelivered: volume / 3e3
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
function calculateHalstead(node) {
|
|
3372
|
+
const operators = /* @__PURE__ */ new Map();
|
|
3373
|
+
const operands = /* @__PURE__ */ new Map();
|
|
3374
|
+
function visit(n) {
|
|
3375
|
+
classifyNode(n, operators, operands);
|
|
3376
|
+
ts4.forEachChild(n, visit);
|
|
3317
3377
|
}
|
|
3378
|
+
ts4.forEachChild(node, visit);
|
|
3379
|
+
return computeHalsteadMetrics(operators, operands);
|
|
3318
3380
|
}
|
|
3319
3381
|
|
|
3320
|
-
// src/commands/
|
|
3321
|
-
function
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
);
|
|
3334
|
-
const commitsByDate = parseGitLogCommits(output, ignore2);
|
|
3335
|
-
let dateCount = 0;
|
|
3336
|
-
let isFirst = true;
|
|
3337
|
-
for (const [date, dateCommits] of commitsByDate) {
|
|
3338
|
-
if (options2.since) {
|
|
3339
|
-
if (date < options2.since) {
|
|
3340
|
-
break;
|
|
3382
|
+
// src/commands/complexity/shared/countSloc.ts
|
|
3383
|
+
function countSloc(content) {
|
|
3384
|
+
let inMultiLineComment = false;
|
|
3385
|
+
let count = 0;
|
|
3386
|
+
for (const line of content.split("\n")) {
|
|
3387
|
+
const trimmed = line.trim();
|
|
3388
|
+
if (inMultiLineComment) {
|
|
3389
|
+
if (trimmed.includes("*/")) {
|
|
3390
|
+
inMultiLineComment = false;
|
|
3391
|
+
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
3392
|
+
if (afterComment.trim().length > 0) {
|
|
3393
|
+
count++;
|
|
3394
|
+
}
|
|
3341
3395
|
}
|
|
3342
|
-
|
|
3343
|
-
break;
|
|
3396
|
+
continue;
|
|
3344
3397
|
}
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3398
|
+
if (trimmed.startsWith("//")) {
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
if (trimmed.startsWith("/*")) {
|
|
3402
|
+
if (trimmed.includes("*/")) {
|
|
3403
|
+
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
3404
|
+
if (afterComment.trim().length > 0) {
|
|
3405
|
+
count++;
|
|
3406
|
+
}
|
|
3407
|
+
} else {
|
|
3408
|
+
inMultiLineComment = true;
|
|
3409
|
+
}
|
|
3410
|
+
continue;
|
|
3411
|
+
}
|
|
3412
|
+
if (trimmed.length > 0) {
|
|
3413
|
+
count++;
|
|
3348
3414
|
}
|
|
3349
|
-
isFirst = false;
|
|
3350
|
-
printDateHeader(date, skipDays.has(date), devlogEntries.get(date));
|
|
3351
|
-
printCommitsWithFiles(dateCommits, ignore2, options2.verbose ?? false);
|
|
3352
3415
|
}
|
|
3416
|
+
return count;
|
|
3353
3417
|
}
|
|
3354
3418
|
|
|
3355
|
-
// src/commands/
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
} catch {
|
|
3366
|
-
return null;
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
function stripToMinor(version2) {
|
|
3370
|
-
const parsed = semver.parse(semver.coerce(version2));
|
|
3371
|
-
return parsed ? `v${parsed.major}.${parsed.minor}` : `v${version2}`;
|
|
3419
|
+
// src/commands/complexity/shared/index.ts
|
|
3420
|
+
function createSourceFromFile(filePath) {
|
|
3421
|
+
const content = fs11.readFileSync(filePath, "utf-8");
|
|
3422
|
+
return ts5.createSourceFile(
|
|
3423
|
+
path16.basename(filePath),
|
|
3424
|
+
content,
|
|
3425
|
+
ts5.ScriptTarget.Latest,
|
|
3426
|
+
true,
|
|
3427
|
+
filePath.endsWith(".tsx") ? ts5.ScriptKind.TSX : ts5.ScriptKind.TS
|
|
3428
|
+
);
|
|
3372
3429
|
}
|
|
3373
|
-
function
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
encoding: "utf-8"
|
|
3379
|
-
}
|
|
3380
|
-
).trim();
|
|
3381
|
-
const [date, hash] = output.split("|");
|
|
3382
|
-
if (!date || !hash) return null;
|
|
3383
|
-
const version2 = getVersionAtCommit(hash);
|
|
3384
|
-
if (!version2) return null;
|
|
3385
|
-
return { date, version: stripToMinor(version2) };
|
|
3386
|
-
} catch {
|
|
3387
|
-
return null;
|
|
3430
|
+
function withSourceFiles(pattern2, callback) {
|
|
3431
|
+
const files = findSourceFiles2(pattern2);
|
|
3432
|
+
if (files.length === 0) {
|
|
3433
|
+
console.log(chalk31.yellow("No files found matching pattern"));
|
|
3434
|
+
return void 0;
|
|
3388
3435
|
}
|
|
3436
|
+
return callback(files);
|
|
3389
3437
|
}
|
|
3390
|
-
function
|
|
3391
|
-
const
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
if (gitInfo) return { date: lastDate, version: gitInfo.version };
|
|
3438
|
+
function forEachFunction(files, callback) {
|
|
3439
|
+
for (const file of files) {
|
|
3440
|
+
const sourceFile = createSourceFromFile(file);
|
|
3441
|
+
const visit = (node) => {
|
|
3442
|
+
if (hasFunctionBody(node)) {
|
|
3443
|
+
callback(file, getNodeName(node), node);
|
|
3444
|
+
}
|
|
3445
|
+
ts5.forEachChild(node, visit);
|
|
3446
|
+
};
|
|
3447
|
+
visit(sourceFile);
|
|
3401
3448
|
}
|
|
3402
|
-
const lastVersion = entries.get(lastDate)?.[0]?.version;
|
|
3403
|
-
return lastVersion ? { date: lastDate, version: lastVersion } : null;
|
|
3404
|
-
}
|
|
3405
|
-
function cleanVersion(version2) {
|
|
3406
|
-
return semver.clean(version2) ?? semver.coerce(version2)?.version ?? null;
|
|
3407
|
-
}
|
|
3408
|
-
function bumpVersion(version2, type) {
|
|
3409
|
-
const cleaned = cleanVersion(version2);
|
|
3410
|
-
if (!cleaned) return version2;
|
|
3411
|
-
const bumped = semver.inc(cleaned, type);
|
|
3412
|
-
if (!bumped) return version2;
|
|
3413
|
-
if (type === "minor") return stripToMinor(bumped);
|
|
3414
|
-
return `v${bumped}`;
|
|
3415
3449
|
}
|
|
3416
3450
|
|
|
3417
|
-
// src/commands/
|
|
3418
|
-
|
|
3419
|
-
|
|
3451
|
+
// src/commands/complexity/cyclomatic.ts
|
|
3452
|
+
async function cyclomatic(pattern2 = "**/*.ts", options2 = {}) {
|
|
3453
|
+
withSourceFiles(pattern2, (files) => {
|
|
3454
|
+
const results = [];
|
|
3455
|
+
let hasViolation = false;
|
|
3456
|
+
forEachFunction(files, (file, name, node) => {
|
|
3457
|
+
const complexity = calculateCyclomaticComplexity(node);
|
|
3458
|
+
results.push({ file, name, complexity });
|
|
3459
|
+
if (options2.threshold !== void 0 && complexity > options2.threshold) {
|
|
3460
|
+
hasViolation = true;
|
|
3461
|
+
}
|
|
3462
|
+
});
|
|
3463
|
+
results.sort((a, b) => b.complexity - a.complexity);
|
|
3464
|
+
for (const { file, name, complexity } of results) {
|
|
3465
|
+
const exceedsThreshold = options2.threshold !== void 0 && complexity > options2.threshold;
|
|
3466
|
+
const color = exceedsThreshold ? chalk32.red : chalk32.white;
|
|
3467
|
+
console.log(`${color(`${file}:${name}`)} \u2192 ${chalk32.cyan(complexity)}`);
|
|
3468
|
+
}
|
|
3469
|
+
console.log(
|
|
3470
|
+
chalk32.dim(
|
|
3471
|
+
`
|
|
3472
|
+
Analyzed ${results.length} functions across ${files.length} files`
|
|
3473
|
+
)
|
|
3474
|
+
);
|
|
3475
|
+
if (hasViolation) {
|
|
3476
|
+
process.exit(1);
|
|
3477
|
+
}
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3420
3480
|
|
|
3421
|
-
// src/commands/
|
|
3422
|
-
import
|
|
3423
|
-
function
|
|
3424
|
-
|
|
3425
|
-
const
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3481
|
+
// src/commands/complexity/halstead.ts
|
|
3482
|
+
import chalk33 from "chalk";
|
|
3483
|
+
async function halstead(pattern2 = "**/*.ts", options2 = {}) {
|
|
3484
|
+
withSourceFiles(pattern2, (files) => {
|
|
3485
|
+
const results = [];
|
|
3486
|
+
let hasViolation = false;
|
|
3487
|
+
forEachFunction(files, (file, name, node) => {
|
|
3488
|
+
const metrics = calculateHalstead(node);
|
|
3489
|
+
results.push({ file, name, metrics });
|
|
3490
|
+
if (options2.threshold !== void 0 && metrics.volume > options2.threshold) {
|
|
3491
|
+
hasViolation = true;
|
|
3492
|
+
}
|
|
3493
|
+
});
|
|
3494
|
+
results.sort((a, b) => b.metrics.effort - a.metrics.effort);
|
|
3495
|
+
for (const { file, name, metrics } of results) {
|
|
3496
|
+
const exceedsThreshold = options2.threshold !== void 0 && metrics.volume > options2.threshold;
|
|
3497
|
+
const color = exceedsThreshold ? chalk33.red : chalk33.white;
|
|
3498
|
+
console.log(
|
|
3499
|
+
`${color(`${file}:${name}`)} \u2192 volume: ${chalk33.cyan(metrics.volume.toFixed(1))}, difficulty: ${chalk33.yellow(metrics.difficulty.toFixed(1))}, effort: ${chalk33.magenta(metrics.effort.toFixed(1))}`
|
|
3500
|
+
);
|
|
3430
3501
|
}
|
|
3431
|
-
} else if (patchVersion && minorVersion) {
|
|
3432
3502
|
console.log(
|
|
3433
|
-
|
|
3503
|
+
chalk33.dim(
|
|
3504
|
+
`
|
|
3505
|
+
Analyzed ${results.length} functions across ${files.length} files`
|
|
3506
|
+
)
|
|
3434
3507
|
);
|
|
3508
|
+
if (hasViolation) {
|
|
3509
|
+
process.exit(1);
|
|
3510
|
+
}
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
// src/commands/complexity/maintainability/index.ts
|
|
3515
|
+
import fs12 from "fs";
|
|
3516
|
+
|
|
3517
|
+
// src/commands/complexity/maintainability/displayMaintainabilityResults.ts
|
|
3518
|
+
import chalk34 from "chalk";
|
|
3519
|
+
function displayMaintainabilityResults(results, threshold) {
|
|
3520
|
+
const filtered = threshold !== void 0 ? results.filter((r) => r.minMaintainability < threshold) : results;
|
|
3521
|
+
if (threshold !== void 0 && filtered.length === 0) {
|
|
3522
|
+
console.log(chalk34.green("All files pass maintainability threshold"));
|
|
3435
3523
|
} else {
|
|
3436
|
-
|
|
3524
|
+
for (const { file, avgMaintainability, minMaintainability } of filtered) {
|
|
3525
|
+
const color = threshold !== void 0 ? chalk34.red : chalk34.white;
|
|
3526
|
+
console.log(
|
|
3527
|
+
`${color(file)} \u2192 avg: ${chalk34.cyan(avgMaintainability.toFixed(1))}, min: ${chalk34.yellow(minMaintainability.toFixed(1))}`
|
|
3528
|
+
);
|
|
3529
|
+
}
|
|
3437
3530
|
}
|
|
3438
|
-
|
|
3531
|
+
console.log(chalk34.dim(`
|
|
3532
|
+
Analyzed ${results.length} files`));
|
|
3533
|
+
if (filtered.length > 0 && threshold !== void 0) {
|
|
3534
|
+
console.error(
|
|
3535
|
+
chalk34.red(
|
|
3536
|
+
`
|
|
3537
|
+
Fail: ${filtered.length} file(s) below threshold ${threshold}. Maintainability index (0\u2013100) is derived from Halstead volume, cyclomatic complexity, and lines of code.
|
|
3439
3538
|
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
minor: bumpVersion(lastInfo.version, "minor")
|
|
3446
|
-
};
|
|
3447
|
-
}
|
|
3448
|
-
function findTargetDate(commitsByDate, skipDays) {
|
|
3449
|
-
return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
|
|
3450
|
-
}
|
|
3451
|
-
function fetchCommitsByDate(ignore2, lastDate) {
|
|
3452
|
-
const output = execSync16(
|
|
3453
|
-
"git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
|
|
3454
|
-
{ encoding: "utf-8" }
|
|
3455
|
-
);
|
|
3456
|
-
return parseGitLogCommits(output, ignore2, lastDate);
|
|
3457
|
-
}
|
|
3458
|
-
function printVersionInfo(config, lastInfo, firstHash) {
|
|
3459
|
-
const versions = computeVersions(lastInfo);
|
|
3460
|
-
displayVersion(
|
|
3461
|
-
!!config.commit?.conventional,
|
|
3462
|
-
firstHash,
|
|
3463
|
-
versions.patch,
|
|
3464
|
-
versions.minor
|
|
3465
|
-
);
|
|
3466
|
-
}
|
|
3467
|
-
function resolveIgnoreList(options2, config) {
|
|
3468
|
-
return options2.ignore ?? config.devlog?.ignore ?? [];
|
|
3469
|
-
}
|
|
3470
|
-
function resolveSkipDays(config) {
|
|
3471
|
-
return new Set(config.devlog?.skip?.days ?? []);
|
|
3472
|
-
}
|
|
3473
|
-
function getLastDate(lastInfo) {
|
|
3474
|
-
return lastInfo?.date ?? null;
|
|
3475
|
-
}
|
|
3476
|
-
function getCommitsForDate(commitsByDate, date) {
|
|
3477
|
-
return commitsByDate.get(date) ?? [];
|
|
3478
|
-
}
|
|
3479
|
-
function noCommitsMessage(hasLastInfo) {
|
|
3480
|
-
return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
|
|
3481
|
-
}
|
|
3482
|
-
function logName(repoName) {
|
|
3483
|
-
console.log(`${chalk40.bold("name:")} ${repoName}`);
|
|
3484
|
-
}
|
|
3485
|
-
function displayNextEntry(ctx, targetDate, commits) {
|
|
3486
|
-
logName(ctx.repoName);
|
|
3487
|
-
printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
|
|
3488
|
-
console.log(chalk40.bold.blue(targetDate));
|
|
3489
|
-
printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
|
|
3490
|
-
}
|
|
3491
|
-
function logNoCommits(lastInfo) {
|
|
3492
|
-
console.log(chalk40.dim(noCommitsMessage(!!lastInfo)));
|
|
3539
|
+
\u26A0\uFE0F ${chalk34.bold("Diagnose and fix one file at a time")} \u2014 do not investigate or fix multiple files in parallel. Run 'assist complexity <file>' to see all metrics. For larger files, start by extracting responsibilities into smaller files.`
|
|
3540
|
+
)
|
|
3541
|
+
);
|
|
3542
|
+
process.exit(1);
|
|
3543
|
+
}
|
|
3493
3544
|
}
|
|
3494
3545
|
|
|
3495
|
-
// src/commands/
|
|
3496
|
-
function
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
const config = loadConfig();
|
|
3503
|
-
const data = resolveContextData(config, options2);
|
|
3504
|
-
return { config, ...data, verbose: options2.verbose ?? false };
|
|
3546
|
+
// src/commands/complexity/maintainability/index.ts
|
|
3547
|
+
function calculateMaintainabilityIndex(halsteadVolume, cyclomaticComplexity, sloc2) {
|
|
3548
|
+
if (halsteadVolume === 0 || sloc2 === 0) {
|
|
3549
|
+
return 100;
|
|
3550
|
+
}
|
|
3551
|
+
const mi = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(sloc2);
|
|
3552
|
+
return Math.max(0, Math.min(100, mi));
|
|
3505
3553
|
}
|
|
3506
|
-
function
|
|
3507
|
-
const
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3554
|
+
function collectFileMetrics(files) {
|
|
3555
|
+
const fileMetrics = /* @__PURE__ */ new Map();
|
|
3556
|
+
for (const file of files) {
|
|
3557
|
+
const content = fs12.readFileSync(file, "utf-8");
|
|
3558
|
+
fileMetrics.set(file, { sloc: countSloc(content), functions: [] });
|
|
3559
|
+
}
|
|
3560
|
+
forEachFunction(files, (file, _name, node) => {
|
|
3561
|
+
const metrics = fileMetrics.get(file);
|
|
3562
|
+
if (metrics) {
|
|
3563
|
+
const complexity = calculateCyclomaticComplexity(node);
|
|
3564
|
+
const halstead2 = calculateHalstead(node);
|
|
3565
|
+
const mi = calculateMaintainabilityIndex(
|
|
3566
|
+
halstead2.volume,
|
|
3567
|
+
complexity,
|
|
3568
|
+
metrics.sloc
|
|
3569
|
+
);
|
|
3570
|
+
metrics.functions.push(mi);
|
|
3571
|
+
}
|
|
3572
|
+
});
|
|
3573
|
+
return fileMetrics;
|
|
3513
3574
|
}
|
|
3514
|
-
function
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3575
|
+
function aggregateResults(fileMetrics) {
|
|
3576
|
+
const results = [];
|
|
3577
|
+
for (const [file, metrics] of fileMetrics) {
|
|
3578
|
+
if (metrics.functions.length === 0) continue;
|
|
3579
|
+
const avgMaintainability = metrics.functions.reduce((a, b) => a + b, 0) / metrics.functions.length;
|
|
3580
|
+
const minMaintainability = Math.min(...metrics.functions);
|
|
3581
|
+
results.push({ file, avgMaintainability, minMaintainability });
|
|
3518
3582
|
}
|
|
3519
|
-
|
|
3583
|
+
results.sort((a, b) => a.minMaintainability - b.minMaintainability);
|
|
3584
|
+
return results;
|
|
3520
3585
|
}
|
|
3521
|
-
function
|
|
3522
|
-
|
|
3523
|
-
|
|
3586
|
+
async function maintainability(pattern2 = "**/*.ts", options2 = {}) {
|
|
3587
|
+
withSourceFiles(pattern2, (files) => {
|
|
3588
|
+
const fileMetrics = collectFileMetrics(files);
|
|
3589
|
+
const results = aggregateResults(fileMetrics);
|
|
3590
|
+
displayMaintainabilityResults(results, options2.threshold);
|
|
3591
|
+
});
|
|
3524
3592
|
}
|
|
3525
3593
|
|
|
3526
|
-
// src/commands/
|
|
3527
|
-
import
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3594
|
+
// src/commands/complexity/sloc.ts
|
|
3595
|
+
import fs13 from "fs";
|
|
3596
|
+
import chalk35 from "chalk";
|
|
3597
|
+
async function sloc(pattern2 = "**/*.ts", options2 = {}) {
|
|
3598
|
+
withSourceFiles(pattern2, (files) => {
|
|
3599
|
+
const results = [];
|
|
3600
|
+
let hasViolation = false;
|
|
3601
|
+
for (const file of files) {
|
|
3602
|
+
const content = fs13.readFileSync(file, "utf-8");
|
|
3603
|
+
const lines = countSloc(content);
|
|
3604
|
+
results.push({ file, lines });
|
|
3605
|
+
if (options2.threshold !== void 0 && lines > options2.threshold) {
|
|
3606
|
+
hasViolation = true;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
results.sort((a, b) => b.lines - a.lines);
|
|
3610
|
+
for (const { file, lines } of results) {
|
|
3611
|
+
const exceedsThreshold = options2.threshold !== void 0 && lines > options2.threshold;
|
|
3612
|
+
const color = exceedsThreshold ? chalk35.red : chalk35.white;
|
|
3613
|
+
console.log(`${color(file)} \u2192 ${chalk35.cyan(lines)} lines`);
|
|
3614
|
+
}
|
|
3615
|
+
const total = results.reduce((sum, r) => sum + r.lines, 0);
|
|
3616
|
+
console.log(
|
|
3617
|
+
chalk35.dim(`
|
|
3618
|
+
Total: ${total} lines across ${files.length} files`)
|
|
3619
|
+
);
|
|
3620
|
+
if (hasViolation) {
|
|
3621
|
+
process.exit(1);
|
|
3622
|
+
}
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
// src/commands/complexity/analyze.ts
|
|
3627
|
+
async function analyze(pattern2) {
|
|
3628
|
+
const searchPattern = pattern2.includes("*") || pattern2.includes("/") ? pattern2 : `**/${pattern2}`;
|
|
3629
|
+
const files = findSourceFiles2(searchPattern);
|
|
3630
|
+
if (files.length === 0) {
|
|
3631
|
+
console.log(chalk36.yellow("No files found matching pattern"));
|
|
3632
|
+
return;
|
|
3532
3633
|
}
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
console.log(
|
|
3634
|
+
if (files.length === 1) {
|
|
3635
|
+
const file = files[0];
|
|
3636
|
+
console.log(chalk36.bold.underline("SLOC"));
|
|
3637
|
+
await sloc(file);
|
|
3638
|
+
console.log();
|
|
3639
|
+
console.log(chalk36.bold.underline("Cyclomatic Complexity"));
|
|
3640
|
+
await cyclomatic(file);
|
|
3641
|
+
console.log();
|
|
3642
|
+
console.log(chalk36.bold.underline("Halstead Metrics"));
|
|
3643
|
+
await halstead(file);
|
|
3644
|
+
console.log();
|
|
3645
|
+
console.log(chalk36.bold.underline("Maintainability Index"));
|
|
3646
|
+
await maintainability(file);
|
|
3539
3647
|
return;
|
|
3540
3648
|
}
|
|
3541
|
-
|
|
3542
|
-
skipDays.sort();
|
|
3543
|
-
skip2.days = skipDays;
|
|
3544
|
-
devlog.skip = skip2;
|
|
3545
|
-
config.devlog = devlog;
|
|
3546
|
-
saveConfig(config);
|
|
3547
|
-
console.log(chalk41.green(`Added ${date} to skip list`));
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
// src/commands/devlog/version.ts
|
|
3551
|
-
import chalk42 from "chalk";
|
|
3552
|
-
function version() {
|
|
3553
|
-
const config = loadConfig();
|
|
3554
|
-
const name = getRepoName();
|
|
3555
|
-
const lastInfo = getLastVersionInfo(name, config);
|
|
3556
|
-
const lastVersion = lastInfo?.version ?? null;
|
|
3557
|
-
const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
|
|
3558
|
-
console.log(`${chalk42.bold("name:")} ${name}`);
|
|
3559
|
-
console.log(`${chalk42.bold("last:")} ${lastVersion ?? chalk42.dim("none")}`);
|
|
3560
|
-
console.log(`${chalk42.bold("next:")} ${nextVersion ?? chalk42.dim("none")}`);
|
|
3649
|
+
await maintainability(searchPattern);
|
|
3561
3650
|
}
|
|
3562
3651
|
|
|
3563
|
-
// src/commands/
|
|
3564
|
-
function
|
|
3565
|
-
const
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3652
|
+
// src/commands/registerComplexity.ts
|
|
3653
|
+
function registerComplexity(program2) {
|
|
3654
|
+
const complexityCommand = program2.command("complexity").description("Analyze TypeScript code complexity metrics").argument("[pattern]").action((pattern2) => {
|
|
3655
|
+
if (!pattern2) {
|
|
3656
|
+
complexityCommand.help();
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
return analyze(pattern2);
|
|
3660
|
+
});
|
|
3661
|
+
complexityCommand.command("cyclomatic [pattern]").description("Calculate cyclomatic complexity per function").option("--threshold <number>", "Max complexity threshold", Number.parseInt).action(cyclomatic);
|
|
3662
|
+
complexityCommand.command("halstead [pattern]").description("Calculate Halstead metrics per function").option("--threshold <number>", "Max volume threshold", Number.parseInt).action(halstead);
|
|
3663
|
+
complexityCommand.command("maintainability [pattern]").description("Calculate maintainability index per file").option(
|
|
3664
|
+
"--threshold <number>",
|
|
3665
|
+
"Min maintainability threshold",
|
|
3569
3666
|
Number.parseInt
|
|
3570
|
-
).
|
|
3571
|
-
|
|
3572
|
-
devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
|
|
3573
|
-
devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
|
|
3667
|
+
).action(maintainability);
|
|
3668
|
+
complexityCommand.command("sloc [pattern]").description("Count source lines of code per file").option("--threshold <number>", "Max lines threshold", Number.parseInt).action(sloc);
|
|
3574
3669
|
}
|
|
3575
3670
|
|
|
3576
|
-
// src/commands/
|
|
3577
|
-
import { existsSync as
|
|
3578
|
-
import
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
}
|
|
3590
|
-
function isGitRepo(dir) {
|
|
3591
|
-
try {
|
|
3592
|
-
const result = execSync17("git rev-parse --show-toplevel", {
|
|
3593
|
-
cwd: dir,
|
|
3594
|
-
stdio: "pipe"
|
|
3595
|
-
}).toString().trim();
|
|
3596
|
-
return resolve3(result) === resolve3(dir);
|
|
3597
|
-
} catch {
|
|
3598
|
-
return false;
|
|
3671
|
+
// src/commands/deploy/redirect.ts
|
|
3672
|
+
import { existsSync as existsSync19, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "fs";
|
|
3673
|
+
import chalk37 from "chalk";
|
|
3674
|
+
var TRAILING_SLASH_SCRIPT = ` <script>
|
|
3675
|
+
if (!window.location.pathname.endsWith('/')) {
|
|
3676
|
+
window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
|
|
3677
|
+
}
|
|
3678
|
+
</script>`;
|
|
3679
|
+
function redirect() {
|
|
3680
|
+
const indexPath = "index.html";
|
|
3681
|
+
if (!existsSync19(indexPath)) {
|
|
3682
|
+
console.log(chalk37.yellow("No index.html found"));
|
|
3683
|
+
return;
|
|
3599
3684
|
}
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
function assertCliExists(cli) {
|
|
3605
|
-
const binary = cli.split(/\s+/)[0];
|
|
3606
|
-
const opts = {
|
|
3607
|
-
encoding: "utf-8",
|
|
3608
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
3609
|
-
};
|
|
3610
|
-
try {
|
|
3611
|
-
execSync18(`command -v ${binary}`, opts);
|
|
3612
|
-
} catch {
|
|
3613
|
-
try {
|
|
3614
|
-
execSync18(`where ${binary}`, opts);
|
|
3615
|
-
} catch {
|
|
3616
|
-
console.error(`CLI "${cli}" not found in PATH`);
|
|
3617
|
-
process.exit(1);
|
|
3618
|
-
}
|
|
3685
|
+
const content = readFileSync16(indexPath, "utf-8");
|
|
3686
|
+
if (content.includes("window.location.pathname.endsWith('/')")) {
|
|
3687
|
+
console.log(chalk37.dim("Trailing slash script already present"));
|
|
3688
|
+
return;
|
|
3619
3689
|
}
|
|
3690
|
+
const headCloseIndex = content.indexOf("</head>");
|
|
3691
|
+
if (headCloseIndex === -1) {
|
|
3692
|
+
console.log(chalk37.red("Could not find </head> tag in index.html"));
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
|
|
3696
|
+
writeFileSync14(indexPath, newContent);
|
|
3697
|
+
console.log(chalk37.green("Added trailing slash redirect to index.html"));
|
|
3620
3698
|
}
|
|
3621
3699
|
|
|
3622
|
-
// src/commands/
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
if (line.startsWith(" W ")) return chalk43.red(line);
|
|
3628
|
-
return line;
|
|
3629
|
-
}).join("\n");
|
|
3700
|
+
// src/commands/registerDeploy.ts
|
|
3701
|
+
function registerDeploy(program2) {
|
|
3702
|
+
const deployCommand = program2.command("deploy").description("Netlify deployment utilities");
|
|
3703
|
+
deployCommand.command("init").description("Initialize Netlify project and configure deployment").action(init5);
|
|
3704
|
+
deployCommand.command("redirect").description("Add trailing slash redirect script to index.html").action(redirect);
|
|
3630
3705
|
}
|
|
3631
3706
|
|
|
3632
|
-
// src/
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
}
|
|
3707
|
+
// src/commands/devlog/list/index.ts
|
|
3708
|
+
import { execSync as execSync16 } from "child_process";
|
|
3709
|
+
import { basename as basename3 } from "path";
|
|
3636
3710
|
|
|
3637
|
-
// src/commands/
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3711
|
+
// src/commands/devlog/shared.ts
|
|
3712
|
+
import { execSync as execSync15 } from "child_process";
|
|
3713
|
+
import chalk38 from "chalk";
|
|
3714
|
+
|
|
3715
|
+
// src/commands/devlog/loadDevlogEntries.ts
|
|
3716
|
+
import { readdirSync, readFileSync as readFileSync17 } from "fs";
|
|
3717
|
+
import { homedir as homedir5 } from "os";
|
|
3718
|
+
import { join as join13 } from "path";
|
|
3719
|
+
var DEVLOG_DIR = join13(homedir5(), "git/blog/src/content/devlog");
|
|
3720
|
+
function loadDevlogEntries(repoName) {
|
|
3721
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3722
|
+
try {
|
|
3723
|
+
const files = readdirSync(DEVLOG_DIR).filter((f) => f.endsWith(".md"));
|
|
3724
|
+
for (const file of files) {
|
|
3725
|
+
const content = readFileSync17(join13(DEVLOG_DIR, file), "utf-8");
|
|
3726
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
3727
|
+
if (frontmatterMatch) {
|
|
3728
|
+
const frontmatter = frontmatterMatch[1];
|
|
3729
|
+
const dateMatch = frontmatter.match(/date:\s*"?(\d{4}-\d{2}-\d{2})"?/);
|
|
3730
|
+
const versionMatch = frontmatter.match(/version:\s*(.+)/);
|
|
3731
|
+
const titleMatch = frontmatter.match(/title:\s*(.+)/);
|
|
3732
|
+
const tagsMatch = frontmatter.match(/tags:\s*\[([^\]]*)\]/);
|
|
3733
|
+
if (dateMatch && versionMatch && titleMatch && tagsMatch) {
|
|
3734
|
+
const tags = tagsMatch[1].split(",").map((t) => t.trim());
|
|
3735
|
+
const firstTag = tags[0];
|
|
3736
|
+
if (firstTag !== repoName) {
|
|
3737
|
+
continue;
|
|
3738
|
+
}
|
|
3739
|
+
const date = dateMatch[1];
|
|
3740
|
+
const version2 = versionMatch[1].trim();
|
|
3741
|
+
const title = titleMatch[1].trim();
|
|
3742
|
+
const existing = entries.get(date) || [];
|
|
3743
|
+
existing.push({ version: version2, title, filename: file });
|
|
3744
|
+
entries.set(date, existing);
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3645
3747
|
}
|
|
3748
|
+
} catch {
|
|
3646
3749
|
}
|
|
3647
|
-
|
|
3648
|
-
{ length: Math.min(concurrency, items.length) },
|
|
3649
|
-
() => worker()
|
|
3650
|
-
);
|
|
3651
|
-
await Promise.all(workers);
|
|
3652
|
-
return results;
|
|
3750
|
+
return entries;
|
|
3653
3751
|
}
|
|
3654
3752
|
|
|
3655
|
-
// src/commands/
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
return
|
|
3664
|
-
}
|
|
3665
|
-
const colonMatch = trimmed.match(/^(\S+?):\s{2,}(.+)/);
|
|
3666
|
-
if (colonMatch && !isSkippable(colonMatch[1])) {
|
|
3667
|
-
return { name: colonMatch[1], description: colonMatch[2].trim() };
|
|
3753
|
+
// src/commands/devlog/shared.ts
|
|
3754
|
+
function getCommitFiles(hash) {
|
|
3755
|
+
try {
|
|
3756
|
+
const output = execSync15(`git show --name-only --format="" ${hash}`, {
|
|
3757
|
+
encoding: "utf-8"
|
|
3758
|
+
});
|
|
3759
|
+
return output.trim().split("\n").filter(Boolean);
|
|
3760
|
+
} catch {
|
|
3761
|
+
return [];
|
|
3668
3762
|
}
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3763
|
+
}
|
|
3764
|
+
function shouldIgnoreCommit(files, ignorePaths) {
|
|
3765
|
+
if (ignorePaths.length === 0 || files.length === 0) {
|
|
3766
|
+
return false;
|
|
3672
3767
|
}
|
|
3673
|
-
|
|
3674
|
-
|
|
3768
|
+
return files.every(
|
|
3769
|
+
(file) => ignorePaths.some((ignorePath) => file.startsWith(ignorePath))
|
|
3770
|
+
);
|
|
3771
|
+
}
|
|
3772
|
+
function printCommitsWithFiles(commits, ignore2, verbose) {
|
|
3773
|
+
for (const commit2 of commits) {
|
|
3774
|
+
console.log(` ${chalk38.yellow(commit2.hash)} ${commit2.message}`);
|
|
3775
|
+
if (verbose) {
|
|
3776
|
+
const visibleFiles = commit2.files.filter(
|
|
3777
|
+
(file) => !ignore2.some((p) => file.startsWith(p))
|
|
3778
|
+
);
|
|
3779
|
+
for (const file of visibleFiles) {
|
|
3780
|
+
console.log(` ${chalk38.dim(file)}`);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3675
3783
|
}
|
|
3676
|
-
return void 0;
|
|
3677
3784
|
}
|
|
3678
|
-
function
|
|
3679
|
-
const
|
|
3680
|
-
|
|
3681
|
-
for (const line of
|
|
3682
|
-
const
|
|
3683
|
-
|
|
3684
|
-
|
|
3785
|
+
function parseGitLogCommits(output, ignore2, afterDate) {
|
|
3786
|
+
const lines = output.trim().split("\n");
|
|
3787
|
+
const commitsByDate = /* @__PURE__ */ new Map();
|
|
3788
|
+
for (const line of lines) {
|
|
3789
|
+
const [date, hash, ...messageParts] = line.split("|");
|
|
3790
|
+
const message = messageParts.join("|");
|
|
3791
|
+
if (afterDate && date <= afterDate) {
|
|
3685
3792
|
continue;
|
|
3686
3793
|
}
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3794
|
+
const files = getCommitFiles(hash);
|
|
3795
|
+
if (!shouldIgnoreCommit(files, ignore2)) {
|
|
3796
|
+
const existing = commitsByDate.get(date) || [];
|
|
3797
|
+
existing.push({ date, hash, message, files });
|
|
3798
|
+
commitsByDate.set(date, existing);
|
|
3690
3799
|
}
|
|
3691
|
-
if (!inCommandSection || !trimmed) continue;
|
|
3692
|
-
if (trimmed.startsWith("-") || trimmed.startsWith("=")) continue;
|
|
3693
|
-
const parsed = parseCommandLine(trimmed);
|
|
3694
|
-
if (parsed) commands.push(parsed);
|
|
3695
3800
|
}
|
|
3696
|
-
return
|
|
3801
|
+
return commitsByDate;
|
|
3697
3802
|
}
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
)
|
|
3702
|
-
|
|
3703
|
-
|
|
3803
|
+
|
|
3804
|
+
// src/commands/devlog/list/printDateHeader.ts
|
|
3805
|
+
import chalk39 from "chalk";
|
|
3806
|
+
function printDateHeader(date, isSkipped, entries) {
|
|
3807
|
+
if (isSkipped) {
|
|
3808
|
+
console.log(`${chalk39.bold.blue(date)} ${chalk39.dim("skipped")}`);
|
|
3809
|
+
} else if (entries && entries.length > 0) {
|
|
3810
|
+
const entryInfo = entries.map((e) => `${chalk39.green(e.version)} ${e.title}`).join(" | ");
|
|
3811
|
+
console.log(`${chalk39.bold.blue(date)} ${entryInfo}`);
|
|
3812
|
+
} else {
|
|
3813
|
+
console.log(`${chalk39.bold.blue(date)} ${chalk39.red("\u26A0 devlog missing")}`);
|
|
3814
|
+
}
|
|
3704
3815
|
}
|
|
3705
3816
|
|
|
3706
|
-
// src/commands/
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3817
|
+
// src/commands/devlog/list/index.ts
|
|
3818
|
+
function list3(options2) {
|
|
3819
|
+
const config = loadConfig();
|
|
3820
|
+
const days = options2.days ?? 30;
|
|
3821
|
+
const ignore2 = options2.ignore ?? config.devlog?.ignore ?? [];
|
|
3822
|
+
const skipDays = new Set(config.devlog?.skip?.days ?? []);
|
|
3823
|
+
const repoName = basename3(process.cwd());
|
|
3824
|
+
const devlogEntries = loadDevlogEntries(repoName);
|
|
3825
|
+
const reverseFlag = options2.reverse ? "--reverse " : "";
|
|
3826
|
+
const limitFlag = options2.reverse ? "" : "-n 500 ";
|
|
3827
|
+
const output = execSync16(
|
|
3828
|
+
`git log ${reverseFlag}${limitFlag}--pretty=format:'%ad|%h|%s' --date=short`,
|
|
3829
|
+
{ encoding: "utf-8" }
|
|
3830
|
+
);
|
|
3831
|
+
const commitsByDate = parseGitLogCommits(output, ignore2);
|
|
3832
|
+
let dateCount = 0;
|
|
3833
|
+
let isFirst = true;
|
|
3834
|
+
for (const [date, dateCommits] of commitsByDate) {
|
|
3835
|
+
if (options2.since) {
|
|
3836
|
+
if (date < options2.since) {
|
|
3837
|
+
break;
|
|
3715
3838
|
}
|
|
3716
|
-
)
|
|
3717
|
-
|
|
3839
|
+
} else if (dateCount >= days) {
|
|
3840
|
+
break;
|
|
3841
|
+
}
|
|
3842
|
+
dateCount++;
|
|
3843
|
+
if (!isFirst) {
|
|
3844
|
+
console.log();
|
|
3845
|
+
}
|
|
3846
|
+
isFirst = false;
|
|
3847
|
+
printDateHeader(date, skipDays.has(date), devlogEntries.get(date));
|
|
3848
|
+
printCommitsWithFiles(dateCommits, ignore2, options2.verbose ?? false);
|
|
3849
|
+
}
|
|
3718
3850
|
}
|
|
3719
3851
|
|
|
3720
|
-
// src/commands/
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
if (!subHelp || !hasSubcommands(subHelp)) {
|
|
3733
|
-
return [{ path: path31, description }];
|
|
3852
|
+
// src/commands/devlog/getLastVersionInfo.ts
|
|
3853
|
+
import { execSync as execSync17 } from "child_process";
|
|
3854
|
+
import semver from "semver";
|
|
3855
|
+
function getVersionAtCommit(hash) {
|
|
3856
|
+
try {
|
|
3857
|
+
const content = execSync17(`git show ${hash}:package.json`, {
|
|
3858
|
+
encoding: "utf-8"
|
|
3859
|
+
});
|
|
3860
|
+
const pkg = JSON.parse(content);
|
|
3861
|
+
return pkg.version ?? null;
|
|
3862
|
+
} catch {
|
|
3863
|
+
return null;
|
|
3734
3864
|
}
|
|
3735
|
-
const children = await discoverAt(cli, path31, depth + 1, p);
|
|
3736
|
-
return children.length > 0 ? children : [{ path: path31, description }];
|
|
3737
|
-
}
|
|
3738
|
-
async function discoverAt(cli, parentPath, depth, p) {
|
|
3739
|
-
if (depth > SAFETY_DEPTH) return [];
|
|
3740
|
-
const helpText = await runHelp([cli, ...parentPath]);
|
|
3741
|
-
if (!helpText) return [];
|
|
3742
|
-
const cmds = parseCommands(helpText);
|
|
3743
|
-
const results = await mapAsync(
|
|
3744
|
-
cmds,
|
|
3745
|
-
CONCURRENCY,
|
|
3746
|
-
(cmd) => resolveCommand(cli, [...parentPath, cmd.name], cmd.description, depth, p)
|
|
3747
|
-
);
|
|
3748
|
-
return results.flat();
|
|
3749
|
-
}
|
|
3750
|
-
async function discoverAll(cli) {
|
|
3751
|
-
const topLevel = parseCommands(await runHelp([cli]));
|
|
3752
|
-
const p = { done: 0, total: topLevel.length };
|
|
3753
|
-
const results = await mapAsync(topLevel, CONCURRENCY, async (cmd) => {
|
|
3754
|
-
showProgress(p, cmd.name);
|
|
3755
|
-
const resolved = await resolveCommand(
|
|
3756
|
-
cli,
|
|
3757
|
-
[cmd.name],
|
|
3758
|
-
cmd.description,
|
|
3759
|
-
1,
|
|
3760
|
-
p
|
|
3761
|
-
);
|
|
3762
|
-
p.done++;
|
|
3763
|
-
showProgress(p, cmd.name);
|
|
3764
|
-
return resolved;
|
|
3765
|
-
});
|
|
3766
|
-
if (interactive) process.stderr.write("\r\x1B[K");
|
|
3767
|
-
return results.flat();
|
|
3768
3865
|
}
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
"list",
|
|
3773
|
-
"show",
|
|
3774
|
-
"view",
|
|
3775
|
-
"export",
|
|
3776
|
-
"get",
|
|
3777
|
-
"diff",
|
|
3778
|
-
"status",
|
|
3779
|
-
"search",
|
|
3780
|
-
"checks",
|
|
3781
|
-
"describe",
|
|
3782
|
-
"inspect",
|
|
3783
|
-
"logs",
|
|
3784
|
-
"cat",
|
|
3785
|
-
"top",
|
|
3786
|
-
"explain",
|
|
3787
|
-
"exists",
|
|
3788
|
-
"browse",
|
|
3789
|
-
"watch"
|
|
3790
|
-
]);
|
|
3791
|
-
var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
3792
|
-
"create",
|
|
3793
|
-
"delete",
|
|
3794
|
-
"import",
|
|
3795
|
-
"set",
|
|
3796
|
-
"update",
|
|
3797
|
-
"merge",
|
|
3798
|
-
"close",
|
|
3799
|
-
"reopen",
|
|
3800
|
-
"edit",
|
|
3801
|
-
"apply",
|
|
3802
|
-
"patch",
|
|
3803
|
-
"drain",
|
|
3804
|
-
"cordon",
|
|
3805
|
-
"taint",
|
|
3806
|
-
"push",
|
|
3807
|
-
"deploy",
|
|
3808
|
-
"add",
|
|
3809
|
-
"remove",
|
|
3810
|
-
"assign",
|
|
3811
|
-
"unassign",
|
|
3812
|
-
"lock",
|
|
3813
|
-
"unlock",
|
|
3814
|
-
"start",
|
|
3815
|
-
"stop",
|
|
3816
|
-
"restart",
|
|
3817
|
-
"enable",
|
|
3818
|
-
"disable",
|
|
3819
|
-
"revoke",
|
|
3820
|
-
"rotate"
|
|
3821
|
-
]);
|
|
3822
|
-
function classifyVerb(verb) {
|
|
3823
|
-
if (READ_VERBS.has(verb)) return "r";
|
|
3824
|
-
if (WRITE_VERBS.has(verb)) return "w";
|
|
3825
|
-
return "?";
|
|
3866
|
+
function stripToMinor(version2) {
|
|
3867
|
+
const parsed = semver.parse(semver.coerce(version2));
|
|
3868
|
+
return parsed ? `v${parsed.major}.${parsed.minor}` : `v${version2}`;
|
|
3826
3869
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3870
|
+
function getLastVersionInfoFromGit() {
|
|
3871
|
+
try {
|
|
3872
|
+
const output = execSync17(
|
|
3873
|
+
"git log -1 --pretty=format:'%ad|%h' --date=short",
|
|
3874
|
+
{
|
|
3875
|
+
encoding: "utf-8"
|
|
3876
|
+
}
|
|
3877
|
+
).trim();
|
|
3878
|
+
const [date, hash] = output.split("|");
|
|
3879
|
+
if (!date || !hash) return null;
|
|
3880
|
+
const version2 = getVersionAtCommit(hash);
|
|
3881
|
+
if (!version2) return null;
|
|
3882
|
+
return { date, version: stripToMinor(version2) };
|
|
3883
|
+
} catch {
|
|
3884
|
+
return null;
|
|
3885
|
+
}
|
|
3833
3886
|
}
|
|
3834
|
-
function
|
|
3835
|
-
const
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
const
|
|
3844
|
-
|
|
3887
|
+
function findLastDate(entries) {
|
|
3888
|
+
const dates = Array.from(entries.keys()).sort().reverse();
|
|
3889
|
+
return dates[0] ?? null;
|
|
3890
|
+
}
|
|
3891
|
+
function getLastVersionInfo(repoName, config) {
|
|
3892
|
+
const entries = loadDevlogEntries(repoName);
|
|
3893
|
+
const lastDate = findLastDate(entries);
|
|
3894
|
+
if (!lastDate) return null;
|
|
3895
|
+
if (config?.commit?.conventional) {
|
|
3896
|
+
const gitInfo = getLastVersionInfoFromGit();
|
|
3897
|
+
if (gitInfo) return { date: lastDate, version: gitInfo.version };
|
|
3845
3898
|
}
|
|
3846
|
-
|
|
3899
|
+
const lastVersion = entries.get(lastDate)?.[0]?.version;
|
|
3900
|
+
return lastVersion ? { date: lastDate, version: lastVersion } : null;
|
|
3901
|
+
}
|
|
3902
|
+
function cleanVersion(version2) {
|
|
3903
|
+
return semver.clean(version2) ?? semver.coerce(version2)?.version ?? null;
|
|
3904
|
+
}
|
|
3905
|
+
function bumpVersion(version2, type) {
|
|
3906
|
+
const cleaned = cleanVersion(version2);
|
|
3907
|
+
if (!cleaned) return version2;
|
|
3908
|
+
const bumped = semver.inc(cleaned, type);
|
|
3909
|
+
if (!bumped) return version2;
|
|
3910
|
+
if (type === "minor") return stripToMinor(bumped);
|
|
3911
|
+
return `v${bumped}`;
|
|
3847
3912
|
}
|
|
3848
3913
|
|
|
3849
|
-
// src/commands/
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
const
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3914
|
+
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
3915
|
+
import { execSync as execSync18 } from "child_process";
|
|
3916
|
+
import chalk41 from "chalk";
|
|
3917
|
+
|
|
3918
|
+
// src/commands/devlog/next/displayNextEntry/displayVersion.ts
|
|
3919
|
+
import chalk40 from "chalk";
|
|
3920
|
+
function displayVersion(conventional, firstHash, patchVersion, minorVersion) {
|
|
3921
|
+
if (conventional && firstHash) {
|
|
3922
|
+
const version2 = getVersionAtCommit(firstHash);
|
|
3923
|
+
if (version2) {
|
|
3924
|
+
console.log(`${chalk40.bold("version:")} ${stripToMinor(version2)}`);
|
|
3925
|
+
} else {
|
|
3926
|
+
console.log(`${chalk40.bold("version:")} ${chalk40.red("unknown")}`);
|
|
3927
|
+
}
|
|
3928
|
+
} else if (patchVersion && minorVersion) {
|
|
3929
|
+
console.log(
|
|
3930
|
+
`${chalk40.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
|
|
3931
|
+
);
|
|
3932
|
+
} else {
|
|
3933
|
+
console.log(`${chalk40.bold("version:")} v0.1 (initial)`);
|
|
3861
3934
|
}
|
|
3862
|
-
return commands;
|
|
3863
3935
|
}
|
|
3864
3936
|
|
|
3865
|
-
// src/commands/
|
|
3866
|
-
function
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3937
|
+
// src/commands/devlog/next/displayNextEntry/index.ts
|
|
3938
|
+
function computeVersions(lastInfo) {
|
|
3939
|
+
if (!lastInfo) return { patch: null, minor: null };
|
|
3940
|
+
return {
|
|
3941
|
+
patch: bumpVersion(lastInfo.version, "patch"),
|
|
3942
|
+
minor: bumpVersion(lastInfo.version, "minor")
|
|
3943
|
+
};
|
|
3944
|
+
}
|
|
3945
|
+
function findTargetDate(commitsByDate, skipDays) {
|
|
3946
|
+
return Array.from(commitsByDate.keys()).filter((d) => !skipDays.has(d)).sort()[0];
|
|
3947
|
+
}
|
|
3948
|
+
function fetchCommitsByDate(ignore2, lastDate) {
|
|
3949
|
+
const output = execSync18(
|
|
3950
|
+
"git log --pretty=format:'%ad|%h|%s' --date=short -n 500",
|
|
3951
|
+
{ encoding: "utf-8" }
|
|
3952
|
+
);
|
|
3953
|
+
return parseGitLogCommits(output, ignore2, lastDate);
|
|
3954
|
+
}
|
|
3955
|
+
function printVersionInfo(config, lastInfo, firstHash) {
|
|
3956
|
+
const versions = computeVersions(lastInfo);
|
|
3957
|
+
displayVersion(
|
|
3958
|
+
!!config.commit?.conventional,
|
|
3959
|
+
firstHash,
|
|
3960
|
+
versions.patch,
|
|
3961
|
+
versions.minor
|
|
3962
|
+
);
|
|
3963
|
+
}
|
|
3964
|
+
function resolveIgnoreList(options2, config) {
|
|
3965
|
+
return options2.ignore ?? config.devlog?.ignore ?? [];
|
|
3966
|
+
}
|
|
3967
|
+
function resolveSkipDays(config) {
|
|
3968
|
+
return new Set(config.devlog?.skip?.days ?? []);
|
|
3969
|
+
}
|
|
3970
|
+
function getLastDate(lastInfo) {
|
|
3971
|
+
return lastInfo?.date ?? null;
|
|
3972
|
+
}
|
|
3973
|
+
function getCommitsForDate(commitsByDate, date) {
|
|
3974
|
+
return commitsByDate.get(date) ?? [];
|
|
3975
|
+
}
|
|
3976
|
+
function noCommitsMessage(hasLastInfo) {
|
|
3977
|
+
return hasLastInfo ? "No commits after last versioned entry" : "No commits found";
|
|
3978
|
+
}
|
|
3979
|
+
function logName(repoName) {
|
|
3980
|
+
console.log(`${chalk41.bold("name:")} ${repoName}`);
|
|
3981
|
+
}
|
|
3982
|
+
function displayNextEntry(ctx, targetDate, commits) {
|
|
3983
|
+
logName(ctx.repoName);
|
|
3984
|
+
printVersionInfo(ctx.config, ctx.lastInfo, commits[0]?.hash);
|
|
3985
|
+
console.log(chalk41.bold.blue(targetDate));
|
|
3986
|
+
printCommitsWithFiles(commits, ctx.ignore, ctx.verbose);
|
|
3987
|
+
}
|
|
3988
|
+
function logNoCommits(lastInfo) {
|
|
3989
|
+
console.log(chalk41.dim(noCommitsMessage(!!lastInfo)));
|
|
3873
3990
|
}
|
|
3874
3991
|
|
|
3875
|
-
// src/commands/
|
|
3876
|
-
function
|
|
3877
|
-
const
|
|
3878
|
-
|
|
3992
|
+
// src/commands/devlog/next/index.ts
|
|
3993
|
+
function resolveContextData(config, options2) {
|
|
3994
|
+
const repoName = getRepoName();
|
|
3995
|
+
const lastInfo = getLastVersionInfo(repoName, config);
|
|
3996
|
+
return { repoName, lastInfo, ignore: resolveIgnoreList(options2, config) };
|
|
3879
3997
|
}
|
|
3880
|
-
function
|
|
3881
|
-
const
|
|
3882
|
-
|
|
3883
|
-
return
|
|
3998
|
+
function buildContext(options2) {
|
|
3999
|
+
const config = loadConfig();
|
|
4000
|
+
const data = resolveContextData(config, options2);
|
|
4001
|
+
return { config, ...data, verbose: options2.verbose ?? false };
|
|
3884
4002
|
}
|
|
3885
|
-
function
|
|
3886
|
-
const
|
|
3887
|
-
|
|
3888
|
-
|
|
4003
|
+
function fetchNextCommits(ctx) {
|
|
4004
|
+
const commitsByDate = fetchCommitsByDate(
|
|
4005
|
+
ctx.ignore,
|
|
4006
|
+
getLastDate(ctx.lastInfo)
|
|
4007
|
+
);
|
|
4008
|
+
const targetDate = findTargetDate(commitsByDate, resolveSkipDays(ctx.config));
|
|
4009
|
+
return targetDate ? { targetDate, commits: getCommitsForDate(commitsByDate, targetDate) } : null;
|
|
3889
4010
|
}
|
|
3890
|
-
|
|
3891
|
-
if (!
|
|
3892
|
-
|
|
3893
|
-
|
|
4011
|
+
function showResult(ctx, found) {
|
|
4012
|
+
if (!found) {
|
|
4013
|
+
logNoCommits(ctx.lastInfo);
|
|
4014
|
+
return;
|
|
3894
4015
|
}
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
4016
|
+
displayNextEntry(ctx, found.targetDate, found.commits);
|
|
4017
|
+
}
|
|
4018
|
+
function next(options2) {
|
|
4019
|
+
const ctx = buildContext(options2);
|
|
4020
|
+
showResult(ctx, fetchNextCommits(ctx));
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
// src/commands/devlog/skip.ts
|
|
4024
|
+
import chalk42 from "chalk";
|
|
4025
|
+
function skip(date) {
|
|
4026
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
4027
|
+
console.log(chalk42.red("Invalid date format. Use YYYY-MM-DD"));
|
|
3900
4028
|
process.exit(1);
|
|
3901
4029
|
}
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
4030
|
+
const config = loadProjectConfig();
|
|
4031
|
+
const devlog = config.devlog ?? {};
|
|
4032
|
+
const skip2 = devlog.skip ?? {};
|
|
4033
|
+
const skipDays = skip2.days ?? [];
|
|
4034
|
+
if (skipDays.includes(date)) {
|
|
4035
|
+
console.log(chalk42.yellow(`${date} is already in skip list`));
|
|
4036
|
+
return;
|
|
3909
4037
|
}
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
4038
|
+
skipDays.push(date);
|
|
4039
|
+
skipDays.sort();
|
|
4040
|
+
skip2.days = skipDays;
|
|
4041
|
+
devlog.skip = skip2;
|
|
4042
|
+
config.devlog = devlog;
|
|
4043
|
+
saveConfig(config);
|
|
4044
|
+
console.log(chalk42.green(`Added ${date} to skip list`));
|
|
3916
4045
|
}
|
|
3917
4046
|
|
|
3918
|
-
// src/commands/
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
4047
|
+
// src/commands/devlog/version.ts
|
|
4048
|
+
import chalk43 from "chalk";
|
|
4049
|
+
function version() {
|
|
4050
|
+
const config = loadConfig();
|
|
4051
|
+
const name = getRepoName();
|
|
4052
|
+
const lastInfo = getLastVersionInfo(name, config);
|
|
4053
|
+
const lastVersion = lastInfo?.version ?? null;
|
|
4054
|
+
const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
|
|
4055
|
+
console.log(`${chalk43.bold("name:")} ${name}`);
|
|
4056
|
+
console.log(`${chalk43.bold("last:")} ${lastVersion ?? chalk43.dim("none")}`);
|
|
4057
|
+
console.log(`${chalk43.bold("next:")} ${nextVersion ?? chalk43.dim("none")}`);
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
// src/commands/registerDevlog.ts
|
|
4061
|
+
function registerDevlog(program2) {
|
|
4062
|
+
const devlogCommand = program2.command("devlog").description("Development log utilities");
|
|
4063
|
+
devlogCommand.command("list").description("Group git commits by date").option(
|
|
4064
|
+
"--days <number>",
|
|
4065
|
+
"Number of days to show (default: 30)",
|
|
4066
|
+
Number.parseInt
|
|
4067
|
+
).option("--since <date>", "Only show commits since this date (YYYY-MM-DD)").option("-r, --reverse", "Show earliest commits first").option("-v, --verbose", "Show file names for each commit").action(list3);
|
|
4068
|
+
devlogCommand.command("version").description("Show current repo name and version info").action(version);
|
|
4069
|
+
devlogCommand.command("next").description("Show commits for the day after the last versioned entry").option("-v, --verbose", "Show file names for each commit").action(next);
|
|
4070
|
+
devlogCommand.command("skip <date>").description("Add a date (YYYY-MM-DD) to the skip list").action(skip);
|
|
3926
4071
|
}
|
|
3927
4072
|
|
|
3928
4073
|
// src/commands/prs/comment.ts
|
|
3929
4074
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3930
4075
|
import { unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "fs";
|
|
3931
4076
|
import { tmpdir as tmpdir2 } from "os";
|
|
3932
|
-
import { join as
|
|
4077
|
+
import { join as join14 } from "path";
|
|
3933
4078
|
|
|
3934
4079
|
// src/commands/prs/shared.ts
|
|
3935
4080
|
import { execSync as execSync19 } from "child_process";
|
|
@@ -4001,7 +4146,7 @@ function comment(path31, line, body) {
|
|
|
4001
4146
|
validateLine(line);
|
|
4002
4147
|
try {
|
|
4003
4148
|
const prId = getCurrentPrNodeId();
|
|
4004
|
-
const queryFile =
|
|
4149
|
+
const queryFile = join14(tmpdir2(), `gh-query-${Date.now()}.graphql`);
|
|
4005
4150
|
writeFileSync15(queryFile, MUTATION);
|
|
4006
4151
|
try {
|
|
4007
4152
|
const result = spawnSync2(
|
|
@@ -4046,26 +4191,26 @@ import { execSync as execSync21 } from "child_process";
|
|
|
4046
4191
|
import { execSync as execSync20 } from "child_process";
|
|
4047
4192
|
import { unlinkSync as unlinkSync5, writeFileSync as writeFileSync16 } from "fs";
|
|
4048
4193
|
import { tmpdir as tmpdir3 } from "os";
|
|
4049
|
-
import { join as
|
|
4194
|
+
import { join as join16 } from "path";
|
|
4050
4195
|
|
|
4051
4196
|
// src/commands/prs/loadCommentsCache.ts
|
|
4052
|
-
import { existsSync as
|
|
4053
|
-
import { join as
|
|
4054
|
-
import { parse } from "yaml";
|
|
4197
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18, unlinkSync as unlinkSync4 } from "fs";
|
|
4198
|
+
import { join as join15 } from "path";
|
|
4199
|
+
import { parse as parse2 } from "yaml";
|
|
4055
4200
|
function getCachePath(prNumber) {
|
|
4056
|
-
return
|
|
4201
|
+
return join15(process.cwd(), ".assist", `pr-${prNumber}-comments.yaml`);
|
|
4057
4202
|
}
|
|
4058
4203
|
function loadCommentsCache(prNumber) {
|
|
4059
4204
|
const cachePath = getCachePath(prNumber);
|
|
4060
|
-
if (!
|
|
4205
|
+
if (!existsSync20(cachePath)) {
|
|
4061
4206
|
return null;
|
|
4062
4207
|
}
|
|
4063
|
-
const content =
|
|
4064
|
-
return
|
|
4208
|
+
const content = readFileSync18(cachePath, "utf-8");
|
|
4209
|
+
return parse2(content);
|
|
4065
4210
|
}
|
|
4066
4211
|
function deleteCommentsCache(prNumber) {
|
|
4067
4212
|
const cachePath = getCachePath(prNumber);
|
|
4068
|
-
if (
|
|
4213
|
+
if (existsSync20(cachePath)) {
|
|
4069
4214
|
unlinkSync4(cachePath);
|
|
4070
4215
|
console.log("No more unresolved line comments. Cache dropped.");
|
|
4071
4216
|
}
|
|
@@ -4080,7 +4225,7 @@ function replyToComment(org, repo, prNumber, commentId, message) {
|
|
|
4080
4225
|
}
|
|
4081
4226
|
function resolveThread(threadId) {
|
|
4082
4227
|
const mutation = `mutation($threadId: ID!) { resolveReviewThread(input: {threadId: $threadId}) { thread { isResolved } } }`;
|
|
4083
|
-
const queryFile =
|
|
4228
|
+
const queryFile = join16(tmpdir3(), `gh-mutation-${Date.now()}.graphql`);
|
|
4084
4229
|
writeFileSync16(queryFile, mutation);
|
|
4085
4230
|
try {
|
|
4086
4231
|
execSync20(
|
|
@@ -4161,18 +4306,18 @@ function fixed(commentId, sha) {
|
|
|
4161
4306
|
}
|
|
4162
4307
|
|
|
4163
4308
|
// src/commands/prs/listComments/index.ts
|
|
4164
|
-
import { existsSync as
|
|
4165
|
-
import { join as
|
|
4309
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync5, writeFileSync as writeFileSync18 } from "fs";
|
|
4310
|
+
import { join as join18 } from "path";
|
|
4166
4311
|
import { stringify } from "yaml";
|
|
4167
4312
|
|
|
4168
4313
|
// src/commands/prs/fetchThreadIds.ts
|
|
4169
4314
|
import { execSync as execSync22 } from "child_process";
|
|
4170
4315
|
import { unlinkSync as unlinkSync6, writeFileSync as writeFileSync17 } from "fs";
|
|
4171
4316
|
import { tmpdir as tmpdir4 } from "os";
|
|
4172
|
-
import { join as
|
|
4317
|
+
import { join as join17 } from "path";
|
|
4173
4318
|
var THREAD_QUERY = `query($owner: String!, $repo: String!, $prNumber: Int!) { repository(owner: $owner, name: $repo) { pullRequest(number: $prNumber) { reviewThreads(first: 100) { nodes { id isResolved comments(first: 100) { nodes { databaseId } } } } } } }`;
|
|
4174
4319
|
function fetchThreadIds(org, repo, prNumber) {
|
|
4175
|
-
const queryFile =
|
|
4320
|
+
const queryFile = join17(tmpdir4(), `gh-query-${Date.now()}.graphql`);
|
|
4176
4321
|
writeFileSync17(queryFile, THREAD_QUERY);
|
|
4177
4322
|
try {
|
|
4178
4323
|
const result = execSync22(
|
|
@@ -4277,8 +4422,8 @@ function printComments(comments) {
|
|
|
4277
4422
|
}
|
|
4278
4423
|
}
|
|
4279
4424
|
function writeCommentsCache(prNumber, comments) {
|
|
4280
|
-
const assistDir =
|
|
4281
|
-
if (!
|
|
4425
|
+
const assistDir = join18(process.cwd(), ".assist");
|
|
4426
|
+
if (!existsSync21(assistDir)) {
|
|
4282
4427
|
mkdirSync5(assistDir, { recursive: true });
|
|
4283
4428
|
}
|
|
4284
4429
|
const cacheData = {
|
|
@@ -4286,7 +4431,7 @@ function writeCommentsCache(prNumber, comments) {
|
|
|
4286
4431
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4287
4432
|
comments
|
|
4288
4433
|
};
|
|
4289
|
-
const cachePath =
|
|
4434
|
+
const cachePath = join18(assistDir, `pr-${prNumber}-comments.yaml`);
|
|
4290
4435
|
writeFileSync18(cachePath, stringify(cacheData));
|
|
4291
4436
|
}
|
|
4292
4437
|
function handleKnownErrors(error) {
|
|
@@ -4665,7 +4810,7 @@ function getViolations(pattern2, options2 = {}, maxLines = DEFAULT_MAX_LINES) {
|
|
|
4665
4810
|
|
|
4666
4811
|
// src/commands/refactor/check/index.ts
|
|
4667
4812
|
function runScript(script, cwd) {
|
|
4668
|
-
return new Promise((
|
|
4813
|
+
return new Promise((resolve6) => {
|
|
4669
4814
|
const child = spawn3("npm", ["run", script], {
|
|
4670
4815
|
stdio: "pipe",
|
|
4671
4816
|
shell: true,
|
|
@@ -4679,7 +4824,7 @@ function runScript(script, cwd) {
|
|
|
4679
4824
|
output += data.toString();
|
|
4680
4825
|
});
|
|
4681
4826
|
child.on("close", (code) => {
|
|
4682
|
-
|
|
4827
|
+
resolve6({ script, code: code ?? 1, output });
|
|
4683
4828
|
});
|
|
4684
4829
|
});
|
|
4685
4830
|
}
|
|
@@ -5251,8 +5396,8 @@ function registerRefactor(program2) {
|
|
|
5251
5396
|
}
|
|
5252
5397
|
|
|
5253
5398
|
// src/commands/transcript/shared.ts
|
|
5254
|
-
import { existsSync as
|
|
5255
|
-
import { basename as basename4, join as
|
|
5399
|
+
import { existsSync as existsSync22, readdirSync as readdirSync2, statSync } from "fs";
|
|
5400
|
+
import { basename as basename4, join as join19, relative } from "path";
|
|
5256
5401
|
import * as readline2 from "readline";
|
|
5257
5402
|
var DATE_PREFIX_REGEX = /^\d{4}-\d{2}-\d{2}/;
|
|
5258
5403
|
function getDatePrefix(daysOffset = 0) {
|
|
@@ -5267,10 +5412,10 @@ function isValidDatePrefix(filename) {
|
|
|
5267
5412
|
return DATE_PREFIX_REGEX.test(filename);
|
|
5268
5413
|
}
|
|
5269
5414
|
function collectFiles(dir, extension) {
|
|
5270
|
-
if (!
|
|
5415
|
+
if (!existsSync22(dir)) return [];
|
|
5271
5416
|
const results = [];
|
|
5272
5417
|
for (const entry of readdirSync2(dir)) {
|
|
5273
|
-
const fullPath =
|
|
5418
|
+
const fullPath = join19(dir, entry);
|
|
5274
5419
|
if (statSync(fullPath).isDirectory()) {
|
|
5275
5420
|
results.push(...collectFiles(fullPath, extension));
|
|
5276
5421
|
} else if (entry.endsWith(extension)) {
|
|
@@ -5302,9 +5447,9 @@ function createReadlineInterface() {
|
|
|
5302
5447
|
});
|
|
5303
5448
|
}
|
|
5304
5449
|
function askQuestion(rl, question) {
|
|
5305
|
-
return new Promise((
|
|
5450
|
+
return new Promise((resolve6) => {
|
|
5306
5451
|
rl.question(question, (answer) => {
|
|
5307
|
-
|
|
5452
|
+
resolve6(answer.trim());
|
|
5308
5453
|
});
|
|
5309
5454
|
});
|
|
5310
5455
|
}
|
|
@@ -5364,14 +5509,14 @@ async function configure() {
|
|
|
5364
5509
|
}
|
|
5365
5510
|
|
|
5366
5511
|
// src/commands/transcript/format/index.ts
|
|
5367
|
-
import { existsSync as
|
|
5512
|
+
import { existsSync as existsSync24 } from "fs";
|
|
5368
5513
|
|
|
5369
5514
|
// src/commands/transcript/format/fixInvalidDatePrefixes/index.ts
|
|
5370
|
-
import { dirname as dirname15, join as
|
|
5515
|
+
import { dirname as dirname15, join as join21 } from "path";
|
|
5371
5516
|
|
|
5372
5517
|
// src/commands/transcript/format/fixInvalidDatePrefixes/promptForDateFix.ts
|
|
5373
5518
|
import { renameSync } from "fs";
|
|
5374
|
-
import { join as
|
|
5519
|
+
import { join as join20 } from "path";
|
|
5375
5520
|
async function resolveDate(rl, choice) {
|
|
5376
5521
|
if (choice === "1") return getDatePrefix(0);
|
|
5377
5522
|
if (choice === "2") return getDatePrefix(-1);
|
|
@@ -5386,7 +5531,7 @@ async function resolveDate(rl, choice) {
|
|
|
5386
5531
|
}
|
|
5387
5532
|
function renameWithPrefix(vttDir, vttFile, prefix2) {
|
|
5388
5533
|
const newFilename = `${prefix2}.${vttFile}`;
|
|
5389
|
-
renameSync(
|
|
5534
|
+
renameSync(join20(vttDir, vttFile), join20(vttDir, newFilename));
|
|
5390
5535
|
console.log(`Renamed to: ${newFilename}`);
|
|
5391
5536
|
return newFilename;
|
|
5392
5537
|
}
|
|
@@ -5420,12 +5565,12 @@ async function fixInvalidDatePrefixes(vttFiles) {
|
|
|
5420
5565
|
const vttFileDir = dirname15(vttFile.absolutePath);
|
|
5421
5566
|
const newFilename = await promptForDateFix(vttFile.filename, vttFileDir);
|
|
5422
5567
|
if (newFilename) {
|
|
5423
|
-
const newRelativePath =
|
|
5568
|
+
const newRelativePath = join21(
|
|
5424
5569
|
dirname15(vttFile.relativePath),
|
|
5425
5570
|
newFilename
|
|
5426
5571
|
);
|
|
5427
5572
|
vttFiles[i] = {
|
|
5428
|
-
absolutePath:
|
|
5573
|
+
absolutePath: join21(vttFileDir, newFilename),
|
|
5429
5574
|
relativePath: newRelativePath,
|
|
5430
5575
|
filename: newFilename
|
|
5431
5576
|
};
|
|
@@ -5438,8 +5583,8 @@ async function fixInvalidDatePrefixes(vttFiles) {
|
|
|
5438
5583
|
}
|
|
5439
5584
|
|
|
5440
5585
|
// src/commands/transcript/format/processVttFile/index.ts
|
|
5441
|
-
import { existsSync as
|
|
5442
|
-
import { basename as basename5, dirname as dirname16, join as
|
|
5586
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync19, writeFileSync as writeFileSync19 } from "fs";
|
|
5587
|
+
import { basename as basename5, dirname as dirname16, join as join22 } from "path";
|
|
5443
5588
|
|
|
5444
5589
|
// src/commands/transcript/cleanText.ts
|
|
5445
5590
|
function cleanText(text) {
|
|
@@ -5649,21 +5794,21 @@ function toMdFilename(vttFilename) {
|
|
|
5649
5794
|
return `${basename5(vttFilename, ".vtt").replace(/\s*Transcription\s*/g, " ").trim()}.md`;
|
|
5650
5795
|
}
|
|
5651
5796
|
function resolveOutputDir(relativeDir, transcriptsDir) {
|
|
5652
|
-
return relativeDir === "." ? transcriptsDir :
|
|
5797
|
+
return relativeDir === "." ? transcriptsDir : join22(transcriptsDir, relativeDir);
|
|
5653
5798
|
}
|
|
5654
5799
|
function buildOutputPaths(vttFile, transcriptsDir) {
|
|
5655
5800
|
const mdFile = toMdFilename(vttFile.filename);
|
|
5656
5801
|
const relativeDir = dirname16(vttFile.relativePath);
|
|
5657
5802
|
const outputDir = resolveOutputDir(relativeDir, transcriptsDir);
|
|
5658
|
-
const outputPath =
|
|
5803
|
+
const outputPath = join22(outputDir, mdFile);
|
|
5659
5804
|
return { outputDir, outputPath, mdFile, relativeDir };
|
|
5660
5805
|
}
|
|
5661
5806
|
function logSkipped(relativeDir, mdFile) {
|
|
5662
|
-
console.log(`Skipping (already exists): ${
|
|
5807
|
+
console.log(`Skipping (already exists): ${join22(relativeDir, mdFile)}`);
|
|
5663
5808
|
return "skipped";
|
|
5664
5809
|
}
|
|
5665
5810
|
function ensureDirectory(dir, label2) {
|
|
5666
|
-
if (!
|
|
5811
|
+
if (!existsSync23(dir)) {
|
|
5667
5812
|
mkdirSync6(dir, { recursive: true });
|
|
5668
5813
|
console.log(`Created ${label2}: ${dir}`);
|
|
5669
5814
|
}
|
|
@@ -5686,7 +5831,7 @@ function logReduction(cueCount, messageCount) {
|
|
|
5686
5831
|
}
|
|
5687
5832
|
function readAndParseCues(inputPath) {
|
|
5688
5833
|
console.log(`Reading: ${inputPath}`);
|
|
5689
|
-
return processCues(
|
|
5834
|
+
return processCues(readFileSync19(inputPath, "utf-8"));
|
|
5690
5835
|
}
|
|
5691
5836
|
function writeFormatted(outputPath, content) {
|
|
5692
5837
|
writeFileSync19(outputPath, content, "utf-8");
|
|
@@ -5699,7 +5844,7 @@ function convertVttToMarkdown(inputPath, outputPath) {
|
|
|
5699
5844
|
logReduction(cues.length, chatMessages.length);
|
|
5700
5845
|
}
|
|
5701
5846
|
function tryProcessVtt(vttFile, paths) {
|
|
5702
|
-
if (
|
|
5847
|
+
if (existsSync23(paths.outputPath))
|
|
5703
5848
|
return logSkipped(paths.relativeDir, paths.mdFile);
|
|
5704
5849
|
convertVttToMarkdown(vttFile.absolutePath, paths.outputPath);
|
|
5705
5850
|
return "processed";
|
|
@@ -5725,7 +5870,7 @@ function processAllFiles(vttFiles, transcriptsDir) {
|
|
|
5725
5870
|
logSummary(counts);
|
|
5726
5871
|
}
|
|
5727
5872
|
function requireVttDir(vttDir) {
|
|
5728
|
-
if (!
|
|
5873
|
+
if (!existsSync24(vttDir)) {
|
|
5729
5874
|
console.error(`VTT directory not found: ${vttDir}`);
|
|
5730
5875
|
process.exit(1);
|
|
5731
5876
|
}
|
|
@@ -5757,18 +5902,18 @@ async function format() {
|
|
|
5757
5902
|
}
|
|
5758
5903
|
|
|
5759
5904
|
// src/commands/transcript/summarise/index.ts
|
|
5760
|
-
import { existsSync as
|
|
5761
|
-
import { basename as basename6, dirname as dirname18, join as
|
|
5905
|
+
import { existsSync as existsSync26 } from "fs";
|
|
5906
|
+
import { basename as basename6, dirname as dirname18, join as join24, relative as relative2 } from "path";
|
|
5762
5907
|
|
|
5763
5908
|
// src/commands/transcript/summarise/processStagedFile/index.ts
|
|
5764
5909
|
import {
|
|
5765
|
-
existsSync as
|
|
5910
|
+
existsSync as existsSync25,
|
|
5766
5911
|
mkdirSync as mkdirSync7,
|
|
5767
|
-
readFileSync as
|
|
5912
|
+
readFileSync as readFileSync20,
|
|
5768
5913
|
renameSync as renameSync2,
|
|
5769
5914
|
rmSync
|
|
5770
5915
|
} from "fs";
|
|
5771
|
-
import { dirname as dirname17, join as
|
|
5916
|
+
import { dirname as dirname17, join as join23 } from "path";
|
|
5772
5917
|
|
|
5773
5918
|
// src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
|
|
5774
5919
|
import chalk51 from "chalk";
|
|
@@ -5797,9 +5942,9 @@ function validateStagedContent(filename, content) {
|
|
|
5797
5942
|
}
|
|
5798
5943
|
|
|
5799
5944
|
// src/commands/transcript/summarise/processStagedFile/index.ts
|
|
5800
|
-
var STAGING_DIR =
|
|
5945
|
+
var STAGING_DIR = join23(process.cwd(), ".assist", "transcript");
|
|
5801
5946
|
function processStagedFile() {
|
|
5802
|
-
if (!
|
|
5947
|
+
if (!existsSync25(STAGING_DIR)) {
|
|
5803
5948
|
return false;
|
|
5804
5949
|
}
|
|
5805
5950
|
const stagedFiles = findMdFilesRecursive(STAGING_DIR);
|
|
@@ -5808,7 +5953,7 @@ function processStagedFile() {
|
|
|
5808
5953
|
}
|
|
5809
5954
|
const { transcriptsDir, summaryDir } = getTranscriptConfig();
|
|
5810
5955
|
const stagedFile = stagedFiles[0];
|
|
5811
|
-
const content =
|
|
5956
|
+
const content = readFileSync20(stagedFile.absolutePath, "utf-8");
|
|
5812
5957
|
validateStagedContent(stagedFile.filename, content);
|
|
5813
5958
|
const stagedBaseName = getTranscriptBaseName(stagedFile.filename);
|
|
5814
5959
|
const transcriptFiles = findMdFilesRecursive(transcriptsDir);
|
|
@@ -5821,9 +5966,9 @@ function processStagedFile() {
|
|
|
5821
5966
|
);
|
|
5822
5967
|
process.exit(1);
|
|
5823
5968
|
}
|
|
5824
|
-
const destPath =
|
|
5969
|
+
const destPath = join23(summaryDir, matchingTranscript.relativePath);
|
|
5825
5970
|
const destDir = dirname17(destPath);
|
|
5826
|
-
if (!
|
|
5971
|
+
if (!existsSync25(destDir)) {
|
|
5827
5972
|
mkdirSync7(destDir, { recursive: true });
|
|
5828
5973
|
}
|
|
5829
5974
|
renameSync2(stagedFile.absolutePath, destPath);
|
|
@@ -5837,7 +5982,7 @@ function processStagedFile() {
|
|
|
5837
5982
|
// src/commands/transcript/summarise/index.ts
|
|
5838
5983
|
function buildRelativeKey(relativePath, baseName) {
|
|
5839
5984
|
const relDir = dirname18(relativePath);
|
|
5840
|
-
return relDir === "." ? baseName :
|
|
5985
|
+
return relDir === "." ? baseName : join24(relDir, baseName);
|
|
5841
5986
|
}
|
|
5842
5987
|
function buildSummaryIndex(summaryDir) {
|
|
5843
5988
|
const summaryFiles = findMdFilesRecursive(summaryDir);
|
|
@@ -5850,7 +5995,7 @@ function buildSummaryIndex(summaryDir) {
|
|
|
5850
5995
|
function summarise() {
|
|
5851
5996
|
processStagedFile();
|
|
5852
5997
|
const { transcriptsDir, summaryDir } = getTranscriptConfig();
|
|
5853
|
-
if (!
|
|
5998
|
+
if (!existsSync26(transcriptsDir)) {
|
|
5854
5999
|
console.log("No transcripts directory found.");
|
|
5855
6000
|
return;
|
|
5856
6001
|
}
|
|
@@ -5871,8 +6016,8 @@ function summarise() {
|
|
|
5871
6016
|
}
|
|
5872
6017
|
const next2 = missing[0];
|
|
5873
6018
|
const outputFilename = `${getTranscriptBaseName(next2.filename)}.md`;
|
|
5874
|
-
const outputPath =
|
|
5875
|
-
const summaryFileDir =
|
|
6019
|
+
const outputPath = join24(STAGING_DIR, outputFilename);
|
|
6020
|
+
const summaryFileDir = join24(summaryDir, dirname18(next2.relativePath));
|
|
5876
6021
|
const relativeTranscriptPath = encodeURI(
|
|
5877
6022
|
relative2(summaryFileDir, next2.absolutePath).replace(/\\/g, "/")
|
|
5878
6023
|
);
|
|
@@ -5918,50 +6063,50 @@ function registerVerify(program2) {
|
|
|
5918
6063
|
|
|
5919
6064
|
// src/commands/voice/devices.ts
|
|
5920
6065
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
5921
|
-
import { join as
|
|
6066
|
+
import { join as join26 } from "path";
|
|
5922
6067
|
|
|
5923
6068
|
// src/commands/voice/shared.ts
|
|
5924
|
-
import { homedir as
|
|
5925
|
-
import { dirname as dirname19, join as
|
|
6069
|
+
import { homedir as homedir6 } from "os";
|
|
6070
|
+
import { dirname as dirname19, join as join25 } from "path";
|
|
5926
6071
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
5927
6072
|
var __dirname7 = dirname19(fileURLToPath6(import.meta.url));
|
|
5928
|
-
var VOICE_DIR =
|
|
6073
|
+
var VOICE_DIR = join25(homedir6(), ".assist", "voice");
|
|
5929
6074
|
var voicePaths = {
|
|
5930
6075
|
dir: VOICE_DIR,
|
|
5931
|
-
pid:
|
|
5932
|
-
log:
|
|
5933
|
-
venv:
|
|
5934
|
-
lock:
|
|
6076
|
+
pid: join25(VOICE_DIR, "voice.pid"),
|
|
6077
|
+
log: join25(VOICE_DIR, "voice.log"),
|
|
6078
|
+
venv: join25(VOICE_DIR, ".venv"),
|
|
6079
|
+
lock: join25(VOICE_DIR, "voice.lock")
|
|
5935
6080
|
};
|
|
5936
6081
|
function getPythonDir() {
|
|
5937
|
-
return
|
|
6082
|
+
return join25(__dirname7, "commands", "voice", "python");
|
|
5938
6083
|
}
|
|
5939
6084
|
function getVenvPython() {
|
|
5940
|
-
return process.platform === "win32" ?
|
|
6085
|
+
return process.platform === "win32" ? join25(voicePaths.venv, "Scripts", "python.exe") : join25(voicePaths.venv, "bin", "python");
|
|
5941
6086
|
}
|
|
5942
6087
|
function getLockDir() {
|
|
5943
6088
|
const config = loadConfig();
|
|
5944
6089
|
return config.voice?.lockDir ?? VOICE_DIR;
|
|
5945
6090
|
}
|
|
5946
6091
|
function getLockFile() {
|
|
5947
|
-
return
|
|
6092
|
+
return join25(getLockDir(), "voice.lock");
|
|
5948
6093
|
}
|
|
5949
6094
|
|
|
5950
6095
|
// src/commands/voice/devices.ts
|
|
5951
6096
|
function devices() {
|
|
5952
|
-
const script =
|
|
6097
|
+
const script = join26(getPythonDir(), "list_devices.py");
|
|
5953
6098
|
spawnSync3(getVenvPython(), [script], { stdio: "inherit" });
|
|
5954
6099
|
}
|
|
5955
6100
|
|
|
5956
6101
|
// src/commands/voice/logs.ts
|
|
5957
|
-
import { existsSync as
|
|
6102
|
+
import { existsSync as existsSync27, readFileSync as readFileSync21 } from "fs";
|
|
5958
6103
|
function logs(options2) {
|
|
5959
|
-
if (!
|
|
6104
|
+
if (!existsSync27(voicePaths.log)) {
|
|
5960
6105
|
console.log("No voice log file found");
|
|
5961
6106
|
return;
|
|
5962
6107
|
}
|
|
5963
6108
|
const count = Number.parseInt(options2.lines ?? "150", 10);
|
|
5964
|
-
const content =
|
|
6109
|
+
const content = readFileSync21(voicePaths.log, "utf-8").trim();
|
|
5965
6110
|
if (!content) {
|
|
5966
6111
|
console.log("Voice log is empty");
|
|
5967
6112
|
return;
|
|
@@ -5984,12 +6129,12 @@ function logs(options2) {
|
|
|
5984
6129
|
// src/commands/voice/setup.ts
|
|
5985
6130
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
5986
6131
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
5987
|
-
import { join as
|
|
6132
|
+
import { join as join28 } from "path";
|
|
5988
6133
|
|
|
5989
6134
|
// src/commands/voice/checkLockFile.ts
|
|
5990
6135
|
import { execSync as execSync27 } from "child_process";
|
|
5991
|
-
import { existsSync as
|
|
5992
|
-
import { join as
|
|
6136
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync22, writeFileSync as writeFileSync20 } from "fs";
|
|
6137
|
+
import { join as join27 } from "path";
|
|
5993
6138
|
function isProcessAlive(pid) {
|
|
5994
6139
|
try {
|
|
5995
6140
|
process.kill(pid, 0);
|
|
@@ -6000,9 +6145,9 @@ function isProcessAlive(pid) {
|
|
|
6000
6145
|
}
|
|
6001
6146
|
function checkLockFile() {
|
|
6002
6147
|
const lockFile = getLockFile();
|
|
6003
|
-
if (!
|
|
6148
|
+
if (!existsSync28(lockFile)) return;
|
|
6004
6149
|
try {
|
|
6005
|
-
const lock = JSON.parse(
|
|
6150
|
+
const lock = JSON.parse(readFileSync22(lockFile, "utf-8"));
|
|
6006
6151
|
if (lock.pid && isProcessAlive(lock.pid)) {
|
|
6007
6152
|
console.error(
|
|
6008
6153
|
`Voice daemon already running (PID ${lock.pid}, env: ${lock.env}). Stop it first with: assist voice stop`
|
|
@@ -6013,7 +6158,7 @@ function checkLockFile() {
|
|
|
6013
6158
|
}
|
|
6014
6159
|
}
|
|
6015
6160
|
function bootstrapVenv() {
|
|
6016
|
-
if (
|
|
6161
|
+
if (existsSync28(getVenvPython())) return;
|
|
6017
6162
|
console.log("Setting up Python environment...");
|
|
6018
6163
|
const pythonDir = getPythonDir();
|
|
6019
6164
|
execSync27(
|
|
@@ -6026,7 +6171,7 @@ function bootstrapVenv() {
|
|
|
6026
6171
|
}
|
|
6027
6172
|
function writeLockFile(pid) {
|
|
6028
6173
|
const lockFile = getLockFile();
|
|
6029
|
-
mkdirSync8(
|
|
6174
|
+
mkdirSync8(join27(lockFile, ".."), { recursive: true });
|
|
6030
6175
|
writeFileSync20(
|
|
6031
6176
|
lockFile,
|
|
6032
6177
|
JSON.stringify({
|
|
@@ -6042,7 +6187,7 @@ function setup() {
|
|
|
6042
6187
|
mkdirSync9(voicePaths.dir, { recursive: true });
|
|
6043
6188
|
bootstrapVenv();
|
|
6044
6189
|
console.log("\nDownloading models...\n");
|
|
6045
|
-
const script =
|
|
6190
|
+
const script = join28(getPythonDir(), "setup_models.py");
|
|
6046
6191
|
const result = spawnSync4(getVenvPython(), [script], {
|
|
6047
6192
|
stdio: "inherit",
|
|
6048
6193
|
env: { ...process.env, VOICE_LOG_FILE: voicePaths.log }
|
|
@@ -6056,7 +6201,7 @@ function setup() {
|
|
|
6056
6201
|
// src/commands/voice/start.ts
|
|
6057
6202
|
import { spawn as spawn4 } from "child_process";
|
|
6058
6203
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync21 } from "fs";
|
|
6059
|
-
import { join as
|
|
6204
|
+
import { join as join29 } from "path";
|
|
6060
6205
|
|
|
6061
6206
|
// src/commands/voice/buildDaemonEnv.ts
|
|
6062
6207
|
function buildDaemonEnv(options2) {
|
|
@@ -6094,7 +6239,7 @@ function start2(options2) {
|
|
|
6094
6239
|
bootstrapVenv();
|
|
6095
6240
|
const debug = options2.debug || options2.foreground || process.platform === "win32";
|
|
6096
6241
|
const env = buildDaemonEnv({ debug });
|
|
6097
|
-
const script =
|
|
6242
|
+
const script = join29(getPythonDir(), "voice_daemon.py");
|
|
6098
6243
|
const python = getVenvPython();
|
|
6099
6244
|
if (options2.foreground) {
|
|
6100
6245
|
spawnForeground(python, script, env);
|
|
@@ -6104,7 +6249,7 @@ function start2(options2) {
|
|
|
6104
6249
|
}
|
|
6105
6250
|
|
|
6106
6251
|
// src/commands/voice/status.ts
|
|
6107
|
-
import { existsSync as
|
|
6252
|
+
import { existsSync as existsSync29, readFileSync as readFileSync23 } from "fs";
|
|
6108
6253
|
function isProcessAlive2(pid) {
|
|
6109
6254
|
try {
|
|
6110
6255
|
process.kill(pid, 0);
|
|
@@ -6114,16 +6259,16 @@ function isProcessAlive2(pid) {
|
|
|
6114
6259
|
}
|
|
6115
6260
|
}
|
|
6116
6261
|
function readRecentLogs(count) {
|
|
6117
|
-
if (!
|
|
6118
|
-
const lines =
|
|
6262
|
+
if (!existsSync29(voicePaths.log)) return [];
|
|
6263
|
+
const lines = readFileSync23(voicePaths.log, "utf-8").trim().split("\n");
|
|
6119
6264
|
return lines.slice(-count);
|
|
6120
6265
|
}
|
|
6121
6266
|
function status() {
|
|
6122
|
-
if (!
|
|
6267
|
+
if (!existsSync29(voicePaths.pid)) {
|
|
6123
6268
|
console.log("Voice daemon: not running (no PID file)");
|
|
6124
6269
|
return;
|
|
6125
6270
|
}
|
|
6126
|
-
const pid = Number.parseInt(
|
|
6271
|
+
const pid = Number.parseInt(readFileSync23(voicePaths.pid, "utf-8").trim(), 10);
|
|
6127
6272
|
const alive = isProcessAlive2(pid);
|
|
6128
6273
|
console.log(`Voice daemon: ${alive ? "running" : "dead"} (PID ${pid})`);
|
|
6129
6274
|
const recent = readRecentLogs(5);
|
|
@@ -6142,13 +6287,13 @@ function status() {
|
|
|
6142
6287
|
}
|
|
6143
6288
|
|
|
6144
6289
|
// src/commands/voice/stop.ts
|
|
6145
|
-
import { existsSync as
|
|
6290
|
+
import { existsSync as existsSync30, readFileSync as readFileSync24, unlinkSync as unlinkSync7 } from "fs";
|
|
6146
6291
|
function stop() {
|
|
6147
|
-
if (!
|
|
6292
|
+
if (!existsSync30(voicePaths.pid)) {
|
|
6148
6293
|
console.log("Voice daemon is not running (no PID file)");
|
|
6149
6294
|
return;
|
|
6150
6295
|
}
|
|
6151
|
-
const pid = Number.parseInt(
|
|
6296
|
+
const pid = Number.parseInt(readFileSync24(voicePaths.pid, "utf-8").trim(), 10);
|
|
6152
6297
|
try {
|
|
6153
6298
|
process.kill(pid, "SIGTERM");
|
|
6154
6299
|
console.log(`Sent SIGTERM to voice daemon (PID ${pid})`);
|
|
@@ -6161,7 +6306,7 @@ function stop() {
|
|
|
6161
6306
|
}
|
|
6162
6307
|
try {
|
|
6163
6308
|
const lockFile = getLockFile();
|
|
6164
|
-
if (
|
|
6309
|
+
if (existsSync30(lockFile)) unlinkSync7(lockFile);
|
|
6165
6310
|
} catch {
|
|
6166
6311
|
}
|
|
6167
6312
|
console.log("Voice daemon stopped");
|
|
@@ -6245,7 +6390,7 @@ function extractCode(url, expectedState) {
|
|
|
6245
6390
|
return code;
|
|
6246
6391
|
}
|
|
6247
6392
|
function waitForCallback(port, expectedState) {
|
|
6248
|
-
return new Promise((
|
|
6393
|
+
return new Promise((resolve6, reject) => {
|
|
6249
6394
|
const timeout = setTimeout(() => {
|
|
6250
6395
|
server.close();
|
|
6251
6396
|
reject(new Error("Authorization timed out after 120 seconds"));
|
|
@@ -6262,7 +6407,7 @@ function waitForCallback(port, expectedState) {
|
|
|
6262
6407
|
const code = extractCode(url, expectedState);
|
|
6263
6408
|
respondHtml(res, 200, "Authorization successful!");
|
|
6264
6409
|
server.close();
|
|
6265
|
-
|
|
6410
|
+
resolve6(code);
|
|
6266
6411
|
} catch (err) {
|
|
6267
6412
|
respondHtml(res, 400, err.message);
|
|
6268
6413
|
server.close();
|
|
@@ -6392,7 +6537,7 @@ import { spawn as spawn5 } from "child_process";
|
|
|
6392
6537
|
|
|
6393
6538
|
// src/commands/run/add.ts
|
|
6394
6539
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync22 } from "fs";
|
|
6395
|
-
import { join as
|
|
6540
|
+
import { join as join30 } from "path";
|
|
6396
6541
|
function findAddIndex() {
|
|
6397
6542
|
const addIndex = process.argv.indexOf("add");
|
|
6398
6543
|
if (addIndex === -1 || addIndex + 2 >= process.argv.length) return -1;
|
|
@@ -6446,7 +6591,7 @@ function saveNewRunConfig(name, command, args) {
|
|
|
6446
6591
|
saveConfig(config);
|
|
6447
6592
|
}
|
|
6448
6593
|
function createCommandFile(name) {
|
|
6449
|
-
const dir =
|
|
6594
|
+
const dir = join30(".claude", "commands");
|
|
6450
6595
|
mkdirSync11(dir, { recursive: true });
|
|
6451
6596
|
const content = `---
|
|
6452
6597
|
description: Run ${name}
|
|
@@ -6454,7 +6599,7 @@ description: Run ${name}
|
|
|
6454
6599
|
|
|
6455
6600
|
Run \`assist run ${name} $ARGUMENTS 2>&1\`.
|
|
6456
6601
|
`;
|
|
6457
|
-
const filePath =
|
|
6602
|
+
const filePath = join30(dir, `${name}.md`);
|
|
6458
6603
|
writeFileSync22(filePath, content);
|
|
6459
6604
|
console.log(`Created command file: ${filePath}`);
|
|
6460
6605
|
}
|
|
@@ -6706,7 +6851,6 @@ program.command("notify").description(
|
|
|
6706
6851
|
"Show notification from Claude Code hook (reads JSON from stdin)"
|
|
6707
6852
|
).action(notify);
|
|
6708
6853
|
program.command("update").description("Update assist to the latest version and sync commands").action(update);
|
|
6709
|
-
registerPermitCliReads(program);
|
|
6710
6854
|
registerCliHook(program);
|
|
6711
6855
|
registerPrs(program);
|
|
6712
6856
|
registerRoam(program);
|