@lucasacoutinho/archer 0.0.0-stage → 0.2.1

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 (50) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +5 -0
  3. package/README.md +459 -2
  4. package/dist/bun/plugin.cjs +85 -0
  5. package/dist/bun/plugin.d.cts +7 -0
  6. package/dist/bun/plugin.d.ts +7 -0
  7. package/dist/bun/plugin.js +11 -0
  8. package/dist/bun.cjs +2238 -0
  9. package/dist/bun.d.cts +13 -0
  10. package/dist/bun.d.ts +13 -0
  11. package/dist/bun.js +28 -0
  12. package/dist/chunk-2AKMBLHG.js +2165 -0
  13. package/dist/chunk-67YEOTXP.js +25 -0
  14. package/dist/chunk-IDVLHNL2.js +46 -0
  15. package/dist/chunk-ZZ6TR4PO.js +201 -0
  16. package/dist/cli.js +2643 -0
  17. package/dist/index.cjs +2484 -0
  18. package/dist/index.d.cts +45 -0
  19. package/dist/index.d.ts +45 -0
  20. package/dist/index.js +54 -0
  21. package/dist/jest/plugin.cjs +85 -0
  22. package/dist/jest/plugin.d.cts +7 -0
  23. package/dist/jest/plugin.d.ts +7 -0
  24. package/dist/jest/plugin.js +11 -0
  25. package/dist/jest.cjs +2237 -0
  26. package/dist/jest.d.cts +13 -0
  27. package/dist/jest.d.ts +13 -0
  28. package/dist/jest.js +27 -0
  29. package/dist/matchers-Cqnbsyxe.d.cts +11 -0
  30. package/dist/matchers-Cqnbsyxe.d.ts +11 -0
  31. package/dist/node.cjs +2237 -0
  32. package/dist/node.d.cts +13 -0
  33. package/dist/node.d.ts +13 -0
  34. package/dist/node.js +27 -0
  35. package/dist/reporters-BbAXMpuW.d.cts +6 -0
  36. package/dist/reporters-xh-HQneS.d.ts +6 -0
  37. package/dist/suite-BwnPDCvU.d.cts +337 -0
  38. package/dist/suite-BwnPDCvU.d.ts +337 -0
  39. package/dist/vitest/plugin.cjs +85 -0
  40. package/dist/vitest/plugin.d.cts +7 -0
  41. package/dist/vitest/plugin.d.ts +7 -0
  42. package/dist/vitest/plugin.js +11 -0
  43. package/dist/vitest.cjs +2531 -0
  44. package/dist/vitest.d.cts +37 -0
  45. package/dist/vitest.d.ts +37 -0
  46. package/dist/vitest.js +139 -0
  47. package/package.json +216 -4
  48. package/skills/archer/SKILL.md +81 -0
  49. package/skills/archer/agents/openai.yaml +4 -0
  50. package/skills/archer/references/rules.md +87 -0
@@ -0,0 +1,25 @@
1
+ import {
2
+ formatResult
3
+ } from "./chunk-IDVLHNL2.js";
4
+
5
+ // src/testing/matchers.ts
6
+ function isArchitectureCheck(value) {
7
+ return typeof value === "object" && value !== null && "check" in value && typeof value.check === "function";
8
+ }
9
+ async function toRespectArchitecture(received) {
10
+ if (!isArchitectureCheck(received)) {
11
+ throw new TypeError(
12
+ "toRespectArchitecture() expects an Archer suite created by createArcher() or arch()."
13
+ );
14
+ }
15
+ const result = await received.check();
16
+ return {
17
+ pass: result.ok,
18
+ message: () => result.ok ? "Expected the architecture check to report at least one finding." : formatResult(result)
19
+ };
20
+ }
21
+ var archerMatchers = { toRespectArchitecture };
22
+
23
+ export {
24
+ archerMatchers
25
+ };
@@ -0,0 +1,46 @@
1
+ // src/reporting/format.ts
2
+ function formatLocation(violation) {
3
+ if (violation.file === void 0) {
4
+ return "";
5
+ }
6
+ if (violation.line === void 0) {
7
+ return violation.file;
8
+ }
9
+ return `${violation.file}:${violation.line}:${violation.column ?? 1}`;
10
+ }
11
+ function formatResult(result) {
12
+ if (result.violations.length === 0) {
13
+ return `Archer checked ${result.filesAnalyzed} files and ${result.dependenciesAnalyzed} dependencies.`;
14
+ }
15
+ const violations = result.violations.map((violation) => {
16
+ const location = formatLocation(violation);
17
+ const prefix = location.length > 0 ? ` ${location}
18
+ ` : " ";
19
+ const source = violation.sourceLine === void 0 ? "" : `
20
+ ${violation.sourceLine.trim()}
21
+ ${" ".repeat(Math.max(0, (violation.column ?? 1) - 1))}^`;
22
+ const chain = violation.importChain === void 0 || violation.importChain.length < 3 ? "" : `
23
+ Import chain: ${violation.importChain.join(" -> ")}`;
24
+ const reason = violation.reason === void 0 ? "" : `
25
+ Because: ${violation.reason}`;
26
+ const id = violation.ruleId === void 0 ? "" : ` [${violation.ruleId}]`;
27
+ return `${prefix} ${violation.severity.toUpperCase()}${id}: ${violation.message}${source}
28
+ Rule: ${violation.rule}${reason}${chain}`;
29
+ });
30
+ return `${result.violations.length} architecture finding${result.violations.length === 1 ? "" : "s"}:
31
+
32
+ ${violations.join("\n\n")}`;
33
+ }
34
+ var ArchitectureError = class extends Error {
35
+ result;
36
+ constructor(result) {
37
+ super(formatResult(result));
38
+ this.name = "ArchitectureError";
39
+ this.result = result;
40
+ }
41
+ };
42
+
43
+ export {
44
+ formatResult,
45
+ ArchitectureError
46
+ };
@@ -0,0 +1,201 @@
1
+ import {
2
+ formatResult
3
+ } from "./chunk-IDVLHNL2.js";
4
+
5
+ // src/reporting/baseline.ts
6
+ import { createHash } from "crypto";
7
+ import { mkdir, readFile, writeFile } from "fs/promises";
8
+ import path from "path";
9
+ function violationFingerprint(violation) {
10
+ const stableDetail = violation.dependency ?? violation.sourceLine;
11
+ const identity = [
12
+ violation.ruleId ?? violation.rule,
13
+ violation.file ?? "",
14
+ stableDetail ?? (violation.ruleId === void 0 ? violation.message : "")
15
+ ].join("\0");
16
+ return createHash("sha256").update(identity).digest("hex").slice(0, 20);
17
+ }
18
+ function createBaseline(result) {
19
+ return {
20
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
21
+ version: 1,
22
+ violations: result.violations.map((violation) => ({
23
+ ...violation.file === void 0 ? {} : { file: violation.file },
24
+ fingerprint: violationFingerprint(violation),
25
+ message: violation.message,
26
+ rule: violation.ruleId ?? violation.rule
27
+ }))
28
+ };
29
+ }
30
+ async function readBaseline(fileName) {
31
+ const parsed = JSON.parse(await readFile(fileName, "utf8"));
32
+ if (typeof parsed !== "object" || parsed === null || !("version" in parsed) || parsed.version !== 1 || !("violations" in parsed) || !Array.isArray(parsed.violations) || parsed.violations.some(
33
+ (entry) => typeof entry !== "object" || entry === null || !("fingerprint" in entry) || typeof entry.fingerprint !== "string"
34
+ )) {
35
+ throw new TypeError(`Unsupported Archer baseline '${fileName}'.`);
36
+ }
37
+ return parsed;
38
+ }
39
+ async function writeBaseline(fileName, baseline) {
40
+ await mkdir(path.dirname(fileName), { recursive: true });
41
+ await writeFile(fileName, `${JSON.stringify(baseline, void 0, 2)}
42
+ `, "utf8");
43
+ }
44
+ function applyBaseline(result, baseline) {
45
+ const known = new Set(baseline.violations.map((entry) => entry.fingerprint));
46
+ const violations = result.violations.filter(
47
+ (violation) => !known.has(violationFingerprint(violation))
48
+ );
49
+ const errors = violations.filter((violation) => violation.severity === "error").length;
50
+ const warnings = violations.filter((violation) => violation.severity === "warning").length;
51
+ const infos = violations.filter((violation) => violation.severity === "info").length;
52
+ return {
53
+ ...result,
54
+ errors,
55
+ infos,
56
+ ok: errors === 0,
57
+ violations,
58
+ warnings
59
+ };
60
+ }
61
+
62
+ // src/reporting/reporters.ts
63
+ function singleLine(value) {
64
+ return value.replaceAll(/\s+/gu, " ").trim();
65
+ }
66
+ function agent(result) {
67
+ const status = result.errors > 0 ? "fail" : result.warnings > 0 ? "warn" : result.infos > 0 ? "info" : "ok";
68
+ const summary = [
69
+ `archer: ${status}`,
70
+ `files=${result.filesAnalyzed}`,
71
+ `dependencies=${result.dependenciesAnalyzed}`,
72
+ `errors=${result.errors}`,
73
+ `warnings=${result.warnings}`,
74
+ `infos=${result.infos}`
75
+ ].join(" ");
76
+ const findings = result.violations.map((violation) => {
77
+ const location = violation.file === void 0 ? "architecture" : `${violation.file}:${violation.line ?? 1}:${violation.column ?? 1}`;
78
+ const fields = [
79
+ `${location}: ${violation.severity} ${singleLine(ruleKey(violation))}: ${singleLine(violation.message)}`,
80
+ `rule=${JSON.stringify(singleLine(violation.rule))}`,
81
+ ...violation.reason === void 0 ? [] : [`reason=${JSON.stringify(singleLine(violation.reason))}`],
82
+ ...violation.dependency === void 0 ? [] : [`dependency=${JSON.stringify(violation.dependency)}`],
83
+ ...violation.sourceLine === void 0 ? [] : [`source=${JSON.stringify(singleLine(violation.sourceLine))}`],
84
+ ...violation.importChain === void 0 ? [] : [`import_chain=${JSON.stringify(violation.importChain)}`]
85
+ ];
86
+ return fields.join(" | ");
87
+ });
88
+ return [summary, ...findings].join("\n");
89
+ }
90
+ function sarifLevel(severity) {
91
+ return severity === "info" ? "note" : severity;
92
+ }
93
+ function ruleKey(violation) {
94
+ return violation.ruleId ?? violation.rule;
95
+ }
96
+ function sarif(result) {
97
+ const rules = [
98
+ ...new Map(
99
+ result.violations.map((violation) => [
100
+ ruleKey(violation),
101
+ {
102
+ id: ruleKey(violation),
103
+ shortDescription: { text: violation.rule }
104
+ }
105
+ ])
106
+ ).values()
107
+ ];
108
+ const results = result.violations.map((violation) => ({
109
+ level: sarifLevel(violation.severity),
110
+ message: { text: violation.message },
111
+ ...violation.file === void 0 ? {} : {
112
+ locations: [
113
+ {
114
+ physicalLocation: {
115
+ artifactLocation: { uri: violation.file },
116
+ ...violation.line === void 0 ? {} : {
117
+ region: {
118
+ startColumn: violation.column ?? 1,
119
+ startLine: violation.line
120
+ }
121
+ }
122
+ }
123
+ }
124
+ ]
125
+ },
126
+ ruleId: ruleKey(violation)
127
+ }));
128
+ return JSON.stringify(
129
+ {
130
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
131
+ runs: [{ results, tool: { driver: { name: "Archer", rules } } }],
132
+ version: "2.1.0"
133
+ },
134
+ void 0,
135
+ 2
136
+ );
137
+ }
138
+ function annotationValue(value) {
139
+ return value.replaceAll("%", "%25").replaceAll("\r", "%0D").replaceAll("\n", "%0A").replaceAll(":", "%3A").replaceAll(",", "%2C");
140
+ }
141
+ function github(result) {
142
+ if (result.violations.length === 0) {
143
+ return formatResult(result);
144
+ }
145
+ return result.violations.map((violation) => {
146
+ const level = violation.severity === "info" ? "notice" : violation.severity;
147
+ const attributes = [
148
+ ...violation.file === void 0 ? [] : [`file=${annotationValue(violation.file)}`],
149
+ ...violation.line === void 0 ? [] : [`line=${violation.line}`],
150
+ ...violation.column === void 0 ? [] : [`col=${violation.column}`],
151
+ `title=${annotationValue(ruleKey(violation))}`
152
+ ];
153
+ return `::${level} ${attributes.join(",")}::${annotationValue(violation.message)}`;
154
+ }).join("\n");
155
+ }
156
+ function dotEscape(value) {
157
+ return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
158
+ }
159
+ function dot(graph) {
160
+ const lines = ["digraph archer {", " rankdir=LR;"];
161
+ for (const sourceModule of graph.modules) {
162
+ lines.push(` "${dotEscape(sourceModule.path)}" [shape=box];`);
163
+ }
164
+ for (const dependency of graph.dependencies) {
165
+ const style = dependency.kind === "type" ? ",style=dashed" : "";
166
+ lines.push(
167
+ ` "${dotEscape(dependency.from)}" -> "${dotEscape(dependency.target)}" [label="${dependency.kind}"${style}];`
168
+ );
169
+ }
170
+ lines.push("}");
171
+ return lines.join("\n");
172
+ }
173
+ function report(name, result, graph) {
174
+ switch (name) {
175
+ case "agent":
176
+ case "llm":
177
+ return agent(result);
178
+ case "pretty":
179
+ return formatResult(result);
180
+ case "json":
181
+ return JSON.stringify(result, void 0, 2);
182
+ case "sarif":
183
+ return sarif(result);
184
+ case "github":
185
+ return github(result);
186
+ case "dot":
187
+ if (graph === void 0) {
188
+ throw new TypeError("The DOT reporter needs an architecture graph.");
189
+ }
190
+ return dot(graph);
191
+ }
192
+ }
193
+
194
+ export {
195
+ violationFingerprint,
196
+ createBaseline,
197
+ readBaseline,
198
+ writeBaseline,
199
+ applyBaseline,
200
+ report
201
+ };