@blzsky/variant 0.2.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.
Files changed (42) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +177 -0
  3. package/dist/build-2LO7GD2S.js +63 -0
  4. package/dist/check-ZTKGGF4M.js +44 -0
  5. package/dist/chunk-3E5LBU3O.js +180 -0
  6. package/dist/chunk-4LLK2S4R.js +630 -0
  7. package/dist/chunk-6J4XDG44.js +14 -0
  8. package/dist/chunk-CXNXZLDG.js +13 -0
  9. package/dist/chunk-DRMASVYN.js +25 -0
  10. package/dist/chunk-GDSZFIHM.js +29 -0
  11. package/dist/chunk-ISRF5F3Q.js +68 -0
  12. package/dist/chunk-KSHWC2XN.js +78 -0
  13. package/dist/chunk-LJDDS4RR.js +91 -0
  14. package/dist/chunk-LXKLCLL6.js +91 -0
  15. package/dist/chunk-MZZOE7L4.js +26 -0
  16. package/dist/chunk-NSNZOH5P.js +31 -0
  17. package/dist/chunk-O4LZKXNZ.js +301 -0
  18. package/dist/chunk-RGD422OM.js +108 -0
  19. package/dist/chunk-RVXUCJYA.js +81 -0
  20. package/dist/chunk-SWPVR2DF.js +468 -0
  21. package/dist/chunk-V22IPJ7A.js +64 -0
  22. package/dist/chunk-V5CTGXMM.js +256 -0
  23. package/dist/chunk-VTJ67TT5.js +238 -0
  24. package/dist/chunk-XTYVVUWW.js +279 -0
  25. package/dist/chunk-XUBWCTJZ.js +100 -0
  26. package/dist/chunk-YYGCSKIQ.js +29 -0
  27. package/dist/cli.d.ts +1 -0
  28. package/dist/cli.js +225 -0
  29. package/dist/diff-GFI6RAEA.js +60 -0
  30. package/dist/doctor-GH4RZVEH.js +127 -0
  31. package/dist/env-QORDD6FQ.js +24 -0
  32. package/dist/impact-AF453PLK.js +702 -0
  33. package/dist/index.d.ts +56 -0
  34. package/dist/index.js +7 -0
  35. package/dist/init-KJIUNGAN.js +158 -0
  36. package/dist/insight-DZ5NQY6D.js +29 -0
  37. package/dist/pr-UFZRZX6S.js +152 -0
  38. package/dist/run-6PNH4QRA.js +40 -0
  39. package/dist/symbol-graph-YYUEMZGR.js +22 -0
  40. package/dist/tsconfig-paths-E3UEDGXZ.js +10 -0
  41. package/dist/workspace-X5FUBTY3.js +186 -0
  42. package/package.json +86 -0
@@ -0,0 +1,29 @@
1
+ // src/core/execution/concurrency.ts
2
+ async function mapLimit(items, limit, fn, stopOnError = true) {
3
+ const results = new Array(items.length);
4
+ let next = 0;
5
+ let firstError = null;
6
+ let stopped = false;
7
+ const worker = async () => {
8
+ while (true) {
9
+ if (stopped) return;
10
+ const i = next++;
11
+ if (i >= items.length) return;
12
+ try {
13
+ results[i] = await fn(items[i], i);
14
+ } catch (err) {
15
+ if (firstError === null) firstError = err;
16
+ if (stopOnError) stopped = true;
17
+ return;
18
+ }
19
+ }
20
+ };
21
+ const workerCount = Math.max(1, Math.min(limit, items.length));
22
+ await Promise.all(Array.from({ length: workerCount }, worker));
23
+ if (firstError !== null) throw firstError;
24
+ return results;
25
+ }
26
+
27
+ export {
28
+ mapLimit
29
+ };
@@ -0,0 +1,68 @@
1
+ import {
2
+ getColors
3
+ } from "./chunk-YYGCSKIQ.js";
4
+ import {
5
+ lines
6
+ } from "./chunk-6J4XDG44.js";
7
+ import {
8
+ getPrinter
9
+ } from "./chunk-V22IPJ7A.js";
10
+
11
+ // src/cli/render/insight.ts
12
+ function statusLabel(result) {
13
+ const colors = getColors();
14
+ if (result.skipped) return colors.dim("SKIP");
15
+ return result.cacheHit ? colors.green("HIT") : colors.red("MISS");
16
+ }
17
+ function renderInsights(summary, printer = getPrinter()) {
18
+ const colors = getColors();
19
+ const results = summary.lastRunResults;
20
+ if (results.length === 0) {
21
+ const entries = Object.entries(summary.cachedStats);
22
+ if (entries.length === 0) {
23
+ lines(printer, "No cached task data yet. Run a task first.");
24
+ return;
25
+ }
26
+ lines(printer, "", "Cached task history:");
27
+ for (const [task, entry] of entries) {
28
+ const duration = entry.lastDurationMs !== void 0 ? `${entry.lastDurationMs}ms` : "unknown";
29
+ const ran = new Date(entry.lastRun).toLocaleString();
30
+ lines(
31
+ printer,
32
+ ` ${task.padEnd(20)} last run: ${ran} duration: ${duration}`
33
+ );
34
+ }
35
+ return;
36
+ }
37
+ const nameWidth = Math.max(...results.map((r) => r.task.length), 4);
38
+ const header = `${"TASK".padEnd(nameWidth)} ${"DURATION".padEnd(10)} STATUS`;
39
+ lines(printer, "", colors.bold(header), "-".repeat(header.length));
40
+ for (const result of results) {
41
+ const duration = result.skipped || result.cacheHit ? "-" : `${result.durationMs}ms`;
42
+ lines(
43
+ printer,
44
+ `${result.task.padEnd(nameWidth)} ${duration.padEnd(10)} ${statusLabel(result)}`
45
+ );
46
+ }
47
+ const percent = (summary.cacheHitRate * 100).toFixed(0);
48
+ lines(
49
+ printer,
50
+ "",
51
+ colors.dim(
52
+ `Total: ${summary.totalDurationMs}ms Cache hit rate: ${percent}%`
53
+ )
54
+ );
55
+ }
56
+ function renderEnv(pm, runtime, framework, printer = getPrinter()) {
57
+ lines(
58
+ printer,
59
+ `Package Manager : ${pm}`,
60
+ `Runtime : ${runtime.primary} (fallback: ${runtime.fallback})`,
61
+ `Framework : ${framework ?? "none detected"}`
62
+ );
63
+ }
64
+
65
+ export {
66
+ renderInsights,
67
+ renderEnv
68
+ };
@@ -0,0 +1,78 @@
1
+ // src/core/graph/tsconfig-paths.ts
2
+ import { access } from "fs/promises";
3
+ import path from "path";
4
+ var MAX_WILDCARDS = 1;
5
+ function toPathAliases(cwd, paths, baseUrl) {
6
+ if (paths === void 0) return [];
7
+ const base = baseUrl ?? cwd;
8
+ const aliases = [];
9
+ for (const [pattern, rawTargets] of Object.entries(paths)) {
10
+ const stars = pattern.split("*").length - 1;
11
+ if (stars > MAX_WILDCARDS) continue;
12
+ const wildcard = stars === 1;
13
+ const [prefix = "", suffix = ""] = wildcard ? pattern.split("*") : [pattern, ""];
14
+ const targets = [];
15
+ for (const target of rawTargets) {
16
+ if (target.split("*").length - 1 > MAX_WILDCARDS) continue;
17
+ const absolute = path.resolve(base, target);
18
+ const relative = path.relative(cwd, absolute).replace(/\\/g, "/");
19
+ if (relative === "" || relative.startsWith("../")) continue;
20
+ targets.push(relative);
21
+ }
22
+ if (targets.length > 0) {
23
+ aliases.push({ prefix, suffix, wildcard, targets });
24
+ }
25
+ }
26
+ aliases.sort((a, b) => b.prefix.length - a.prefix.length);
27
+ return aliases;
28
+ }
29
+ function matchPathAlias(spec, aliases) {
30
+ const bases = [];
31
+ for (const alias of aliases) {
32
+ if (!alias.wildcard) {
33
+ if (spec === alias.prefix) bases.push(...alias.targets);
34
+ continue;
35
+ }
36
+ if (spec.length < alias.prefix.length + alias.suffix.length || !spec.startsWith(alias.prefix) || !spec.endsWith(alias.suffix)) {
37
+ continue;
38
+ }
39
+ const captured = spec.slice(
40
+ alias.prefix.length,
41
+ spec.length - alias.suffix.length
42
+ );
43
+ for (const target of alias.targets) {
44
+ bases.push(target.replace("*", captured));
45
+ }
46
+ }
47
+ return bases;
48
+ }
49
+ async function readPathAliases(cwd, tsconfigName = "tsconfig.json") {
50
+ const configPath = path.join(cwd, tsconfigName);
51
+ try {
52
+ await access(configPath);
53
+ } catch {
54
+ return [];
55
+ }
56
+ const { ts } = await import("ts-morph");
57
+ const read = ts.readConfigFile(configPath, (file) => ts.sys.readFile(file));
58
+ if (read.error !== void 0 || read.config === void 0) return [];
59
+ const parsed = ts.parseJsonConfigFileContent(
60
+ read.config,
61
+ {
62
+ ...ts.sys,
63
+ // Enumerating every file in the workspace is pure waste: only
64
+ // `compilerOptions` is wanted, and a large repo makes this the most
65
+ // expensive call in the command.
66
+ readDirectory: () => [],
67
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames
68
+ },
69
+ cwd
70
+ );
71
+ return toPathAliases(cwd, parsed.options.paths, parsed.options.baseUrl);
72
+ }
73
+
74
+ export {
75
+ toPathAliases,
76
+ matchPathAlias,
77
+ readPathAliases
78
+ };
@@ -0,0 +1,91 @@
1
+ import {
2
+ collectExportedSurface,
3
+ significantText
4
+ } from "./chunk-O4LZKXNZ.js";
5
+
6
+ // src/core/semantic/differ.ts
7
+ function createClassifier() {
8
+ let host;
9
+ let nextId = 0;
10
+ return async (input) => {
11
+ host ??= (async () => {
12
+ const tsm2 = await import("ts-morph");
13
+ return {
14
+ tsm: tsm2,
15
+ project: new tsm2.Project({ useInMemoryFileSystem: true })
16
+ };
17
+ })();
18
+ const { tsm, project } = await host;
19
+ const id = nextId++;
20
+ const beforeFile = project.createSourceFile(
21
+ `__before_${id}.ts`,
22
+ input.before,
23
+ { overwrite: true }
24
+ );
25
+ const afterFile = project.createSourceFile(
26
+ `__after_${id}.ts`,
27
+ input.after,
28
+ { overwrite: true }
29
+ );
30
+ try {
31
+ return compareSurfaces(input, beforeFile, afterFile, tsm);
32
+ } finally {
33
+ project.removeSourceFile(beforeFile);
34
+ project.removeSourceFile(afterFile);
35
+ }
36
+ };
37
+ }
38
+ var defaultClassifier;
39
+ async function classifyChange(input) {
40
+ defaultClassifier ??= createClassifier();
41
+ return defaultClassifier(input);
42
+ }
43
+ function compareSurfaces(input, beforeFile, afterFile, tsm) {
44
+ const beforeApi = collectExportedSurface(beforeFile, tsm);
45
+ const afterApi = collectExportedSurface(afterFile, tsm);
46
+ const added = [];
47
+ const removed = [];
48
+ const changed = [];
49
+ for (const [name, after] of afterApi.symbols) {
50
+ const before = beforeApi.symbols.get(name);
51
+ if (before === void 0) {
52
+ added.push(name);
53
+ } else if (before.signature !== after.signature) {
54
+ changed.push({
55
+ name,
56
+ kind: before.typeSpace && after.typeSpace ? "type" : "signature"
57
+ });
58
+ } else if (before.body !== after.body) {
59
+ changed.push({ name, kind: "body" });
60
+ }
61
+ }
62
+ for (const name of beforeApi.symbols.keys()) {
63
+ if (!afterApi.symbols.has(name)) removed.push(name);
64
+ }
65
+ const surfaceChanged = added.length > 0 || removed.length > 0 || changed.some((c) => c.kind !== "body");
66
+ let classification;
67
+ if (surfaceChanged) {
68
+ classification = "breaking";
69
+ } else if (changed.length > 0 || significantText(input.before, tsm) !== significantText(input.after, tsm)) {
70
+ classification = "internal";
71
+ } else {
72
+ classification = "non-impacting";
73
+ }
74
+ const confidenceNotes = [.../* @__PURE__ */ new Set([...beforeApi.notes, ...afterApi.notes])];
75
+ const confidence = Math.max(
76
+ 0.3,
77
+ Math.round((1 - confidenceNotes.length * 0.15) * 100) / 100
78
+ );
79
+ return {
80
+ filePath: input.filePath,
81
+ classification,
82
+ exportedSymbols: { added, removed, changed },
83
+ confidence,
84
+ confidenceNotes
85
+ };
86
+ }
87
+
88
+ export {
89
+ createClassifier,
90
+ classifyChange
91
+ };
@@ -0,0 +1,91 @@
1
+ import {
2
+ CacheError
3
+ } from "./chunk-RVXUCJYA.js";
4
+
5
+ // src/core/cache/store.ts
6
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "fs";
7
+ import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
8
+ import path from "path";
9
+ var CACHE_FILENAME = "cache.json";
10
+ async function readCache(cacheDir) {
11
+ const filePath = path.join(cacheDir, CACHE_FILENAME);
12
+ try {
13
+ const raw = await readFile(filePath, "utf8");
14
+ return JSON.parse(raw);
15
+ } catch (err) {
16
+ if (err.code === "ENOENT") return { tasks: {} };
17
+ throw new CacheError(
18
+ `Failed to read cache at "${filePath}": ${String(err)}`,
19
+ { cause: err }
20
+ );
21
+ }
22
+ }
23
+ async function writeCache(cacheDir, cache) {
24
+ const filePath = path.join(cacheDir, CACHE_FILENAME);
25
+ const tempPath = temporaryPathFor(filePath);
26
+ try {
27
+ await mkdir(cacheDir, { recursive: true });
28
+ await writeFile(tempPath, JSON.stringify(cache));
29
+ await rename(tempPath, filePath);
30
+ } catch (err) {
31
+ await unlink(tempPath).catch(() => {
32
+ });
33
+ throw new CacheError(
34
+ `Failed to write cache to "${cacheDir}": ${String(err)}`,
35
+ { cause: err }
36
+ );
37
+ }
38
+ }
39
+ function writeCacheSync(cacheDir, cache) {
40
+ const filePath = path.join(cacheDir, CACHE_FILENAME);
41
+ const tempPath = temporaryPathFor(filePath);
42
+ try {
43
+ mkdirSync(cacheDir, { recursive: true });
44
+ writeFileSync(tempPath, JSON.stringify(cache));
45
+ renameSync(tempPath, filePath);
46
+ } catch (err) {
47
+ try {
48
+ unlinkSync(tempPath);
49
+ } catch {
50
+ }
51
+ throw new CacheError(
52
+ `Failed to write cache to "${cacheDir}": ${String(err)}`,
53
+ { cause: err }
54
+ );
55
+ }
56
+ }
57
+ var temporaryCounter = 0;
58
+ function temporaryPathFor(filePath) {
59
+ return `${filePath}.${process.pid}.${temporaryCounter++}.tmp`;
60
+ }
61
+ function evictStaleEntries(cache, ttlDays) {
62
+ const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1e3;
63
+ const tasks = {};
64
+ for (const [name, entry] of Object.entries(cache.tasks)) {
65
+ if (entry.lastRun >= cutoff) {
66
+ tasks[name] = entry;
67
+ }
68
+ }
69
+ return { tasks };
70
+ }
71
+
72
+ // src/core/insight/analyzer.ts
73
+ function computeInsights(results, cache) {
74
+ const totalDurationMs = results.reduce((sum, r) => sum + r.durationMs, 0);
75
+ const hits = results.filter((r) => r.cacheHit).length;
76
+ const cacheHitRate = results.length === 0 ? 0 : hits / results.length;
77
+ return {
78
+ lastRunResults: results,
79
+ cachedStats: cache.tasks,
80
+ totalDurationMs,
81
+ cacheHitRate
82
+ };
83
+ }
84
+
85
+ export {
86
+ readCache,
87
+ writeCache,
88
+ writeCacheSync,
89
+ evictStaleEntries,
90
+ computeInsights
91
+ };
@@ -0,0 +1,26 @@
1
+ import {
2
+ packageForFile
3
+ } from "./chunk-3E5LBU3O.js";
4
+ import {
5
+ listChangedFiles
6
+ } from "./chunk-XUBWCTJZ.js";
7
+
8
+ // src/core/cache/git-diff.ts
9
+ import path from "path";
10
+ async function getChangedFiles(options) {
11
+ const { cwd, baseRef = "HEAD~1" } = options;
12
+ return listChangedFiles(cwd, baseRef);
13
+ }
14
+ function changedFilesToPackages(files, graph, cwd) {
15
+ const out = /* @__PURE__ */ new Set();
16
+ for (const file of files) {
17
+ const owner = packageForFile(path.resolve(cwd, file), graph);
18
+ if (owner !== null) out.add(owner.name);
19
+ }
20
+ return out;
21
+ }
22
+
23
+ export {
24
+ getChangedFiles,
25
+ changedFilesToPackages
26
+ };
@@ -0,0 +1,31 @@
1
+ import {
2
+ classifyChange
3
+ } from "./chunk-LJDDS4RR.js";
4
+ import {
5
+ readFileAtRef
6
+ } from "./chunk-XUBWCTJZ.js";
7
+
8
+ // src/core/semantic/file-change.ts
9
+ import { readFile } from "fs/promises";
10
+ import path from "path";
11
+ var DEFAULT_DIFF_BASE_REF = "HEAD~1";
12
+ function toWorkspaceRelative(cwd, filePath) {
13
+ return path.relative(cwd, path.resolve(cwd, filePath)).replace(/\\/g, "/");
14
+ }
15
+ async function classifyFileAgainstRef(cwd, relPath, baseRef) {
16
+ const [before, after] = await Promise.all([
17
+ readFileAtRef(cwd, baseRef, relPath),
18
+ readFile(path.resolve(cwd, relPath), "utf8").catch(() => null)
19
+ ]);
20
+ return classifyChange({
21
+ filePath: relPath,
22
+ before: before ?? "",
23
+ after: after ?? ""
24
+ });
25
+ }
26
+
27
+ export {
28
+ DEFAULT_DIFF_BASE_REF,
29
+ toWorkspaceRelative,
30
+ classifyFileAgainstRef
31
+ };