@esbenwiberg/archmap 0.1.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 (58) hide show
  1. package/README.md +361 -0
  2. package/dist/bin/archmap.d.ts +3 -0
  3. package/dist/bin/archmap.d.ts.map +1 -0
  4. package/dist/bin/archmap.js +72 -0
  5. package/dist/bin/archmap.js.map +1 -0
  6. package/dist/src/cache.d.ts +30 -0
  7. package/dist/src/cache.d.ts.map +1 -0
  8. package/dist/src/cache.js +78 -0
  9. package/dist/src/cache.js.map +1 -0
  10. package/dist/src/churn.d.ts +6 -0
  11. package/dist/src/churn.d.ts.map +1 -0
  12. package/dist/src/churn.js +32 -0
  13. package/dist/src/churn.js.map +1 -0
  14. package/dist/src/classify.d.ts +16 -0
  15. package/dist/src/classify.d.ts.map +1 -0
  16. package/dist/src/classify.js +60 -0
  17. package/dist/src/classify.js.map +1 -0
  18. package/dist/src/commands/check.d.ts +5 -0
  19. package/dist/src/commands/check.d.ts.map +1 -0
  20. package/dist/src/commands/check.js +33 -0
  21. package/dist/src/commands/check.js.map +1 -0
  22. package/dist/src/commands/classify.d.ts +5 -0
  23. package/dist/src/commands/classify.d.ts.map +1 -0
  24. package/dist/src/commands/classify.js +31 -0
  25. package/dist/src/commands/classify.js.map +1 -0
  26. package/dist/src/commands/explain.d.ts +5 -0
  27. package/dist/src/commands/explain.d.ts.map +1 -0
  28. package/dist/src/commands/explain.js +34 -0
  29. package/dist/src/commands/explain.js.map +1 -0
  30. package/dist/src/commands/export.d.ts +48 -0
  31. package/dist/src/commands/export.d.ts.map +1 -0
  32. package/dist/src/commands/export.js +82 -0
  33. package/dist/src/commands/export.js.map +1 -0
  34. package/dist/src/commands/risk.d.ts +6 -0
  35. package/dist/src/commands/risk.d.ts.map +1 -0
  36. package/dist/src/commands/risk.js +27 -0
  37. package/dist/src/commands/risk.js.map +1 -0
  38. package/dist/src/commands/scan.d.ts +4 -0
  39. package/dist/src/commands/scan.d.ts.map +1 -0
  40. package/dist/src/commands/scan.js +37 -0
  41. package/dist/src/commands/scan.js.map +1 -0
  42. package/dist/src/config.d.ts +43 -0
  43. package/dist/src/config.d.ts.map +1 -0
  44. package/dist/src/config.js +87 -0
  45. package/dist/src/config.js.map +1 -0
  46. package/dist/src/files.d.ts +2 -0
  47. package/dist/src/files.d.ts.map +1 -0
  48. package/dist/src/files.js +31 -0
  49. package/dist/src/files.js.map +1 -0
  50. package/dist/src/graph.d.ts +11 -0
  51. package/dist/src/graph.d.ts.map +1 -0
  52. package/dist/src/graph.js +40 -0
  53. package/dist/src/graph.js.map +1 -0
  54. package/dist/src/risk.d.ts +11 -0
  55. package/dist/src/risk.d.ts.map +1 -0
  56. package/dist/src/risk.js +37 -0
  57. package/dist/src/risk.js.map +1 -0
  58. package/package.json +40 -0
@@ -0,0 +1,60 @@
1
+ import { matchOverride } from "./config.js";
2
+ export function classifyFile(file, topology, config, riskScores) {
3
+ const riskScore = riskScores?.get(file) ?? null;
4
+ const override = matchOverride(file, config.overrides);
5
+ if (override) {
6
+ const node = topology.files[file];
7
+ const ca = node?.ca ?? 0;
8
+ const tca = node?.tca ?? 0;
9
+ const ce = node?.ce ?? 0;
10
+ const total = ca + ce;
11
+ return {
12
+ file,
13
+ class: override.classification,
14
+ ca,
15
+ tca,
16
+ instability: total === 0 ? 0 : ce / total,
17
+ risk: riskScore,
18
+ reason: override.reason,
19
+ overridden: true,
20
+ };
21
+ }
22
+ const node = topology.files[file];
23
+ if (!node) {
24
+ process.stderr.write(`warning: "${file}" not found in dependency graph — treating as leaf\n`);
25
+ return {
26
+ file,
27
+ class: "leaf",
28
+ ca: 0,
29
+ tca: 0,
30
+ instability: 0,
31
+ risk: riskScore,
32
+ reason: "not in dependency graph",
33
+ overridden: false,
34
+ };
35
+ }
36
+ const { ca, tca, ce } = node;
37
+ const total = ca + ce;
38
+ const instability = total === 0 ? 0 : ce / total;
39
+ let klass;
40
+ if (ca <= config.thresholds.leaf) {
41
+ klass = "leaf";
42
+ }
43
+ else if (ca <= config.thresholds.junction) {
44
+ klass = "branch";
45
+ }
46
+ else {
47
+ klass = "hub";
48
+ }
49
+ return {
50
+ file,
51
+ class: klass,
52
+ ca,
53
+ tca,
54
+ instability,
55
+ risk: riskScore,
56
+ reason: `Ca=${ca} (${ca} direct, ${tca} transitive)`,
57
+ overridden: false,
58
+ };
59
+ }
60
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/classify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAiB5C,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,QAAkB,EAClB,MAAqB,EACrB,UAAmC;IAEnC,MAAM,SAAS,GAAG,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAChD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAEvD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;QAC3B,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACtB,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,QAAQ,CAAC,cAAc;YAC9B,EAAE;YACF,GAAG;YACH,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK;YACzC,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,UAAU,EAAE,IAAI;SACjB,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,aAAa,IAAI,sDAAsD,CACxE,CAAC;QACF,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,MAAM;YACb,EAAE,EAAE,CAAC;YACL,GAAG,EAAE,CAAC;YACN,WAAW,EAAE,CAAC;YACd,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,yBAAyB;YACjC,UAAU,EAAE,KAAK;SAClB,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC;IAC7B,MAAM,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;IACtB,MAAM,WAAW,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC;IAEjD,IAAI,KAAY,CAAC;IACjB,IAAI,EAAE,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACjC,KAAK,GAAG,MAAM,CAAC;IACjB,CAAC;SAAM,IAAI,EAAE,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QAC5C,KAAK,GAAG,QAAQ,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,KAAK,CAAC;IAChB,CAAC;IAED,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,KAAK;QACZ,EAAE;QACF,GAAG;QACH,WAAW;QACX,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc;QACpD,UAAU,EAAE,KAAK;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function checkCommand(files: string[], opts: {
2
+ json?: boolean;
3
+ config?: string;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/commands/check.ts"],"names":[],"mappings":"AAQA,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -0,0 +1,33 @@
1
+ import { resolveProject } from "../config.js";
2
+ import { classifyFile } from "../classify.js";
3
+ import { getFreshTopology, getFreshChurn } from "../cache.js";
4
+ import { buildTopology } from "../graph.js";
5
+ import { buildChurnMap } from "../churn.js";
6
+ import { computeRiskScores } from "../risk.js";
7
+ export async function checkCommand(files, opts) {
8
+ const { config, entry } = resolveProject(opts.config);
9
+ const { topology } = await getFreshTopology(entry, buildTopology);
10
+ const { churn: churnMap } = getFreshChurn(90, buildChurnMap);
11
+ const riskScores = computeRiskScores(topology, churnMap);
12
+ const results = files.map((f) => classifyFile(f, topology, config, riskScores));
13
+ const hubs = results.filter((r) => r.class === "hub");
14
+ const hasHubs = hubs.length > 0;
15
+ if (opts.json) {
16
+ console.log(JSON.stringify({ hasHubs, hubs, all: results }, null, 2));
17
+ }
18
+ else {
19
+ if (hasHubs) {
20
+ console.log(`load-bearing (hub) files detected:`);
21
+ for (const h of hubs) {
22
+ const riskLabel = h.risk ? ` risk=${h.risk.risk}/100` : "";
23
+ console.log(` ${h.file} Ca=${h.ca} (${h.tca} transitive)${riskLabel}`);
24
+ }
25
+ }
26
+ else {
27
+ console.log("No hub files in changeset.");
28
+ }
29
+ }
30
+ if (hasHubs)
31
+ process.exit(1);
32
+ }
33
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAe,EACf,IAAyC;IAEzC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAElE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEzD,MAAM,OAAO,GAAqB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChD,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAC9C,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAEhC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACxE,CAAC;SAAM,CAAC;QACN,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;YAClD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,eAAe,SAAS,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,IAAI,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function classifyCommand(file: string, opts: {
2
+ json?: boolean;
3
+ config?: string;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../../src/commands/classify.ts"],"names":[],"mappings":"AAQA,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -0,0 +1,31 @@
1
+ import { resolveProject } from "../config.js";
2
+ import { classifyFile } from "../classify.js";
3
+ import { getFreshTopology, getFreshChurn } from "../cache.js";
4
+ import { buildTopology } from "../graph.js";
5
+ import { buildChurnMap } from "../churn.js";
6
+ import { computeRiskScores } from "../risk.js";
7
+ export async function classifyCommand(file, opts) {
8
+ const { config, entry } = resolveProject(opts.config);
9
+ const { topology, cacheHit } = await getFreshTopology(entry, buildTopology);
10
+ if (!opts.json) {
11
+ process.stderr.write(cacheHit ? "(cache hit)\n" : "(topology rebuilt)\n");
12
+ }
13
+ const { churn: churnMap } = getFreshChurn(90, buildChurnMap);
14
+ const riskScores = computeRiskScores(topology, churnMap);
15
+ const result = classifyFile(file, topology, config, riskScores);
16
+ if (opts.json) {
17
+ console.log(JSON.stringify(result, null, 2));
18
+ }
19
+ else {
20
+ const instPct = (result.instability * 100).toFixed(0);
21
+ console.log(`${result.file}`);
22
+ console.log(` class: ${result.class}${result.overridden ? " (overridden)" : ""}`);
23
+ console.log(` ca: ${result.ca} direct, ${result.tca} transitive`);
24
+ console.log(` instability: ${instPct}%`);
25
+ if (result.risk) {
26
+ console.log(` risk: ${result.risk.risk}/100 (structural=${result.risk.structural}, churn=${result.risk.commits} commits/90d)`);
27
+ }
28
+ console.log(` reason: ${result.reason}`);
29
+ }
30
+ }
31
+ //# sourceMappingURL=classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../../src/commands/classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAG/C,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,IAAyC;IAEzC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAE5E,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACzD,MAAM,MAAM,GAAmB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IAEhF,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzF,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,EAAE,YAAY,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC;QAC5E,OAAO,CAAC,GAAG,CAAC,kBAAkB,OAAO,GAAG,CAAC,CAAC;QAC1C,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,IAAI,CAAC,IAAI,qBAAqB,MAAM,CAAC,IAAI,CAAC,UAAU,WAAW,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC,CAAC;QAC1I,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACjD,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function explainCommand(file: string, opts: {
2
+ json?: boolean;
3
+ config?: string;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../../src/commands/explain.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,OAAO,CAAC,IAAI,CAAC,CAkCf"}
@@ -0,0 +1,34 @@
1
+ import { resolveProject } from "../config.js";
2
+ import { classifyFile } from "../classify.js";
3
+ import { getFreshTopology } from "../cache.js";
4
+ import { buildTopology } from "../graph.js";
5
+ export async function explainCommand(file, opts) {
6
+ const { config, entry } = resolveProject(opts.config);
7
+ const { topology } = await getFreshTopology(entry, buildTopology);
8
+ const node = topology.files[file];
9
+ const classification = classifyFile(file, topology, config);
10
+ if (opts.json) {
11
+ console.log(JSON.stringify({
12
+ file,
13
+ classification,
14
+ dependents: node?.dependents ?? [],
15
+ }, null, 2));
16
+ }
17
+ else {
18
+ console.log(`${file}`);
19
+ console.log(` class: ${classification.class}${classification.overridden ? " (overridden)" : ""}`);
20
+ console.log(` reason: ${classification.reason}`);
21
+ console.log(` ca: ${classification.ca} (${classification.ca} file${classification.ca === 1 ? "" : "s"} depend on this)`);
22
+ const deps = node?.dependents ?? [];
23
+ if (deps.length === 0) {
24
+ console.log(" dependents: none");
25
+ }
26
+ else {
27
+ console.log(" dependents:");
28
+ for (const d of deps) {
29
+ console.log(` ${d}`);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAY,EACZ,IAAyC;IAEzC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAElE,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,cAAc,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAE5D,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ;YACE,IAAI;YACJ,cAAc;YACd,UAAU,EAAE,IAAI,EAAE,UAAU,IAAI,EAAE;SACnC,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,gBAAgB,cAAc,CAAC,KAAK,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvG,OAAO,CAAC,GAAG,CAAC,gBAAgB,cAAc,CAAC,MAAM,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,gBAAgB,cAAc,CAAC,EAAE,KAAK,cAAc,CAAC,EAAE,QAAQ,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC;QACjI,MAAM,IAAI,GAAG,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { ArchmapConfig } from "../config.js";
2
+ import type { Topology } from "../graph.js";
3
+ import type { RiskScore } from "../risk.js";
4
+ import type { Klass } from "../classify.js";
5
+ /** One file's fully-resolved verdict — everything an external consumer needs. */
6
+ export interface ExportedFile {
7
+ class: Klass;
8
+ ca: number;
9
+ tca: number;
10
+ instability: number;
11
+ risk: number | null;
12
+ overridden: boolean;
13
+ reason: string;
14
+ dependents: string[];
15
+ }
16
+ /**
17
+ * Self-contained artifact for consumers that have no source tree (e.g. a
18
+ * hosted diff-only review bot). Keyed by `commit` so a consumer can verify it
19
+ * matches the PR head SHA before trusting the lookups.
20
+ *
21
+ * When built with a `scope` (the PR's changed paths), `files` is narrowed to
22
+ * just those paths — each still carrying its full `dependents` blast radius —
23
+ * and the `scope` block records which requested paths were not in the graph,
24
+ * so a missed lookup is loud rather than silent.
25
+ */
26
+ export interface ExportArtifact {
27
+ version: 1;
28
+ commit: string | null;
29
+ generatedAt: string;
30
+ scope?: {
31
+ requested: string[];
32
+ missing: string[];
33
+ };
34
+ files: Record<string, ExportedFile>;
35
+ }
36
+ /**
37
+ * Pure transform: topology + config + risk → a fully-classified artifact.
38
+ * No git, no graph build, no I/O — so it is trivially unit-testable.
39
+ *
40
+ * If `scope` is given, the graph is still built whole-repo (the caller does
41
+ * that), but the emitted `files` map is narrowed to the in-scope paths.
42
+ */
43
+ export declare function buildExportArtifact(topology: Topology, config: ArchmapConfig, riskScores: Map<string, RiskScore>, commit: string | null, scope?: string[], now?: Date): ExportArtifact;
44
+ export declare function exportCommand(opts: {
45
+ config?: string;
46
+ scope?: string;
47
+ }): Promise<void>;
48
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../../../src/commands/export.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACnD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACrC;AAOD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,aAAa,EACrB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAClC,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,GAAG,GAAE,IAAiB,GACrB,cAAc,CAmChB;AAsBD,wBAAsB,aAAa,CAAC,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5F"}
@@ -0,0 +1,82 @@
1
+ import { execSync } from "child_process";
2
+ import { readFileSync } from "fs";
3
+ import { resolveProject } from "../config.js";
4
+ import { classifyFile } from "../classify.js";
5
+ import { getFreshTopology, getFreshChurn } from "../cache.js";
6
+ import { buildTopology } from "../graph.js";
7
+ import { buildChurnMap } from "../churn.js";
8
+ import { computeRiskScores } from "../risk.js";
9
+ /** Normalize an incoming path to the form dependency-cruiser uses as keys. */
10
+ function normalizePath(p) {
11
+ return p.trim().replace(/^\.\//, "");
12
+ }
13
+ /**
14
+ * Pure transform: topology + config + risk → a fully-classified artifact.
15
+ * No git, no graph build, no I/O — so it is trivially unit-testable.
16
+ *
17
+ * If `scope` is given, the graph is still built whole-repo (the caller does
18
+ * that), but the emitted `files` map is narrowed to the in-scope paths.
19
+ */
20
+ export function buildExportArtifact(topology, config, riskScores, commit, scope, now = new Date()) {
21
+ const keys = scope === undefined
22
+ ? Object.keys(topology.files)
23
+ : scope.map(normalizePath).filter((p) => p in topology.files);
24
+ const files = {};
25
+ for (const file of keys) {
26
+ const c = classifyFile(file, topology, config, riskScores);
27
+ files[file] = {
28
+ class: c.class,
29
+ ca: c.ca,
30
+ tca: c.tca,
31
+ instability: c.instability,
32
+ risk: c.risk?.risk ?? null,
33
+ overridden: c.overridden,
34
+ reason: c.reason,
35
+ dependents: topology.files[file].dependents,
36
+ };
37
+ }
38
+ const artifact = {
39
+ version: 1,
40
+ commit,
41
+ generatedAt: now.toISOString(),
42
+ files,
43
+ };
44
+ if (scope !== undefined) {
45
+ const requested = scope.map(normalizePath).filter((p) => p.length > 0);
46
+ artifact.scope = {
47
+ requested,
48
+ missing: requested.filter((p) => !(p in topology.files)),
49
+ };
50
+ }
51
+ return artifact;
52
+ }
53
+ function currentCommit() {
54
+ try {
55
+ return execSync("git rev-parse HEAD", {
56
+ encoding: "utf8",
57
+ stdio: ["pipe", "pipe", "ignore"],
58
+ }).trim();
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Read a newline-delimited path list from a file, or from stdin when "-". */
65
+ function readScope(source) {
66
+ const raw = readFileSync(source === "-" ? 0 : source, "utf8");
67
+ return raw
68
+ .split("\n")
69
+ .map((l) => l.trim())
70
+ .filter((l) => l.length > 0);
71
+ }
72
+ export async function exportCommand(opts) {
73
+ const { config, entry } = resolveProject(opts.config);
74
+ const { topology } = await getFreshTopology(entry, buildTopology);
75
+ const { churn: churnMap } = getFreshChurn(90, buildChurnMap);
76
+ const riskScores = computeRiskScores(topology, churnMap);
77
+ const scope = opts.scope !== undefined ? readScope(opts.scope) : undefined;
78
+ const artifact = buildExportArtifact(topology, config, riskScores, currentCommit(), scope);
79
+ // export is inherently machine-readable: always emit JSON on stdout.
80
+ console.log(JSON.stringify(artifact, null, 2));
81
+ }
82
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/commands/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function riskCommand(opts: {
2
+ top?: string;
3
+ json?: boolean;
4
+ config?: string;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=risk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk.d.ts","sourceRoot":"","sources":["../../../src/commands/risk.ts"],"names":[],"mappings":"AAMA,wBAAsB,WAAW,CAC/B,IAAI,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAgCf"}
@@ -0,0 +1,27 @@
1
+ import { resolveProject } from "../config.js";
2
+ import { getFreshTopology, getFreshChurn } from "../cache.js";
3
+ import { buildTopology } from "../graph.js";
4
+ import { buildChurnMap } from "../churn.js";
5
+ import { computeRiskScores } from "../risk.js";
6
+ export async function riskCommand(opts) {
7
+ const { entry } = resolveProject(opts.config);
8
+ const { topology } = await getFreshTopology(entry, buildTopology);
9
+ const { churn: churnMap } = getFreshChurn(90, buildChurnMap);
10
+ const riskScores = computeRiskScores(topology, churnMap);
11
+ const n = opts.top ? parseInt(opts.top, 10) : 10;
12
+ const sorted = [...riskScores.entries()]
13
+ .sort((a, b) => b[1].risk - a[1].risk)
14
+ .slice(0, n);
15
+ if (opts.json) {
16
+ console.log(JSON.stringify(sorted.map(([file, score]) => ({ file, ...score })), null, 2));
17
+ }
18
+ else {
19
+ console.log(`Top ${sorted.length} riskiest files:\n`);
20
+ for (const [file, score] of sorted) {
21
+ const node = topology.files[file];
22
+ console.log(` ${score.risk.toString().padStart(3)}/100 ${file}`);
23
+ console.log(` ca=${node?.ca ?? 0} tca=${score.tca} churn=${score.commits}c/90d structural=${score.structural}`);
24
+ }
25
+ }
26
+ }
27
+ //# sourceMappingURL=risk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk.js","sourceRoot":"","sources":["../../../src/commands/risk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAuD;IAEvD,MAAM,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IAElE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEzD,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;SACrC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEf,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,EACnD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,MAAM,oBAAoB,CAAC,CAAC;QACtD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO,CAAC,GAAG,CACT,KAAK,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CACtD,CAAC;YACF,OAAO,CAAC,GAAG,CACT,eAAe,IAAI,EAAE,EAAE,IAAI,CAAC,SAAS,KAAK,CAAC,GAAG,WAAW,KAAK,CAAC,OAAO,qBAAqB,KAAK,CAAC,UAAU,EAAE,CAC9G,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function scanCommand(entry: string, opts: {
2
+ json?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/commands/scan.ts"],"names":[],"mappings":"AAOA,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GACvB,OAAO,CAAC,IAAI,CAAC,CAef"}
@@ -0,0 +1,37 @@
1
+ import { writeFileSync, mkdirSync } from "fs";
2
+ import { buildTopology } from "../graph.js";
3
+ import { writeCache } from "../cache.js";
4
+ import { createHash } from "crypto";
5
+ import { readFileSync } from "fs";
6
+ import { listTypeScriptFiles } from "../files.js";
7
+ export async function scanCommand(entry, opts) {
8
+ const topology = await buildTopology(entry);
9
+ mkdirSync(".archmap", { recursive: true });
10
+ writeFileSync(".archmap/topology.json", JSON.stringify(topology, null, 2));
11
+ // update cache
12
+ const hash = computeHash(entry);
13
+ writeCache(hash, topology);
14
+ if (opts.json) {
15
+ console.log(JSON.stringify({ ok: true, files: Object.keys(topology.files).length }));
16
+ }
17
+ else {
18
+ console.log(`Scanned ${Object.keys(topology.files).length} files → .archmap/topology.json`);
19
+ }
20
+ }
21
+ function computeHash(entry) {
22
+ const files = listTypeScriptFiles(entry);
23
+ const hasher = createHash("sha256");
24
+ for (const file of files) {
25
+ hasher.update(file + "\n");
26
+ try {
27
+ const src = readFileSync(file, "utf8");
28
+ const imports = src.match(/^(import|export).*from\s+['"].*['"]/gm) ?? [];
29
+ hasher.update(imports.join("\n") + "\n");
30
+ }
31
+ catch {
32
+ // skip unreadable files
33
+ }
34
+ }
35
+ return hasher.digest("hex");
36
+ }
37
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/commands/scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAa,EACb,IAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;IAE5C,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,aAAa,CAAC,wBAAwB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAE3E,eAAe;IACf,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAChC,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAE3B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvF,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,iCAAiC,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,KAAK,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACvC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,uCAAuC,CAAC,IAAI,EAAE,CAAC;YACzE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { Klass } from "./classify.js";
2
+ export interface Override {
3
+ path: string;
4
+ classification: Klass;
5
+ reason: string;
6
+ }
7
+ export interface ArchmapConfig {
8
+ version: number;
9
+ thresholds: {
10
+ leaf: number;
11
+ junction: number;
12
+ };
13
+ overrides: Override[];
14
+ analyzers: Array<{
15
+ lang: string;
16
+ entry: string;
17
+ }>;
18
+ }
19
+ export declare function loadConfig(configPath?: string): ArchmapConfig;
20
+ /**
21
+ * Walk up from `startDir` to the filesystem root looking for `.archmap.yaml`.
22
+ * Returns the absolute path to the nearest config, or null if none is found.
23
+ */
24
+ export declare function findConfigPath(startDir?: string): string | null;
25
+ export interface ResolvedProject {
26
+ config: ArchmapConfig;
27
+ entry: string;
28
+ root: string;
29
+ configPath: string | null;
30
+ }
31
+ /**
32
+ * Resolve the project context for a command.
33
+ *
34
+ * - With an explicit `--config` path: legacy behaviour — the config is loaded
35
+ * as given and `entry` paths resolve relative to the current working dir.
36
+ * - Without one: walk up from cwd to discover the nearest `.archmap.yaml`,
37
+ * chdir into its directory (the project root), and resolve `entry` paths
38
+ * relative to that directory. This lets `archmap` be run from any
39
+ * subdirectory of a project and still analyse the whole tree.
40
+ */
41
+ export declare function resolveProject(explicitConfig?: string): ResolvedProject;
42
+ export declare function matchOverride(file: string, overrides: Override[]): Override | undefined;
43
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE3C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,KAAK,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AASD,wBAAgB,UAAU,CAAC,UAAU,SAAkB,GAAG,aAAa,CA2BtE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAS9E;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,eAAe,CAkBvE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,QAAQ,EAAE,GACpB,QAAQ,GAAG,SAAS,CAOtB"}
@@ -0,0 +1,87 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { dirname, join, resolve } from "path";
3
+ import { parse } from "yaml";
4
+ import micromatch from "micromatch";
5
+ const DEFAULTS = {
6
+ version: 1,
7
+ thresholds: { leaf: 2, junction: 10 },
8
+ overrides: [],
9
+ analyzers: [{ lang: "typescript", entry: "src/" }],
10
+ };
11
+ export function loadConfig(configPath = ".archmap.yaml") {
12
+ let raw = {};
13
+ try {
14
+ raw = parse(readFileSync(configPath, "utf8")) ?? {};
15
+ }
16
+ catch {
17
+ // no config file — use defaults
18
+ }
19
+ const config = {
20
+ version: raw.version ?? DEFAULTS.version,
21
+ thresholds: {
22
+ leaf: raw.thresholds?.leaf ?? DEFAULTS.thresholds.leaf,
23
+ junction: raw.thresholds?.junction ?? DEFAULTS.thresholds.junction,
24
+ },
25
+ overrides: raw.overrides ?? [],
26
+ analyzers: raw.analyzers ?? DEFAULTS.analyzers,
27
+ };
28
+ for (const ov of config.overrides) {
29
+ if (!["leaf", "branch", "hub"].includes(ov.classification)) {
30
+ throw new Error(`Invalid classification "${ov.classification}" in overrides for path "${ov.path}"`);
31
+ }
32
+ }
33
+ return config;
34
+ }
35
+ /**
36
+ * Walk up from `startDir` to the filesystem root looking for `.archmap.yaml`.
37
+ * Returns the absolute path to the nearest config, or null if none is found.
38
+ */
39
+ export function findConfigPath(startDir = process.cwd()) {
40
+ let dir = resolve(startDir);
41
+ while (true) {
42
+ const candidate = join(dir, ".archmap.yaml");
43
+ if (existsSync(candidate))
44
+ return candidate;
45
+ const parent = dirname(dir);
46
+ if (parent === dir)
47
+ return null; // reached filesystem root
48
+ dir = parent;
49
+ }
50
+ }
51
+ /**
52
+ * Resolve the project context for a command.
53
+ *
54
+ * - With an explicit `--config` path: legacy behaviour — the config is loaded
55
+ * as given and `entry` paths resolve relative to the current working dir.
56
+ * - Without one: walk up from cwd to discover the nearest `.archmap.yaml`,
57
+ * chdir into its directory (the project root), and resolve `entry` paths
58
+ * relative to that directory. This lets `archmap` be run from any
59
+ * subdirectory of a project and still analyse the whole tree.
60
+ */
61
+ export function resolveProject(explicitConfig) {
62
+ let configPath = null;
63
+ let root = process.cwd();
64
+ if (explicitConfig) {
65
+ configPath = explicitConfig;
66
+ }
67
+ else {
68
+ configPath = findConfigPath();
69
+ if (configPath) {
70
+ root = dirname(resolve(configPath));
71
+ if (root !== process.cwd())
72
+ process.chdir(root);
73
+ }
74
+ }
75
+ const config = loadConfig(configPath ?? undefined);
76
+ const entry = config.analyzers.find((a) => a.lang === "typescript")?.entry ?? "src/";
77
+ return { config, entry, root, configPath };
78
+ }
79
+ export function matchOverride(file, overrides) {
80
+ for (const ov of overrides) {
81
+ if (micromatch.isMatch(file, ov.path, { dot: true })) {
82
+ return ov;
83
+ }
84
+ }
85
+ return undefined;
86
+ }
87
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function listTypeScriptFiles(entry: string): string[];
2
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/files.ts"],"names":[],"mappings":"AAOA,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA2B3D"}
@@ -0,0 +1,31 @@
1
+ import { readdirSync, statSync } from "fs";
2
+ import { join } from "path";
3
+ const TS_FILE = /\.(ts|tsx)$/;
4
+ const TEST_FILE = /\.(test|spec)\.(ts|tsx)$/;
5
+ const SKIP_DIRS = new Set(["node_modules", "dist", ".archmap"]);
6
+ export function listTypeScriptFiles(entry) {
7
+ const files = [];
8
+ function walk(path) {
9
+ let stat;
10
+ try {
11
+ stat = statSync(path);
12
+ }
13
+ catch {
14
+ return;
15
+ }
16
+ if (stat.isDirectory()) {
17
+ for (const child of readdirSync(path)) {
18
+ if (!SKIP_DIRS.has(child)) {
19
+ walk(join(path, child));
20
+ }
21
+ }
22
+ return;
23
+ }
24
+ if (stat.isFile() && TS_FILE.test(path) && !TEST_FILE.test(path)) {
25
+ files.push(path);
26
+ }
27
+ }
28
+ walk(entry);
29
+ return files.sort();
30
+ }
31
+ //# sourceMappingURL=files.js.map