@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,37 @@
1
+ import { TestOptions } from 'vitest';
2
+ import { R as ReporterName } from './reporters-BbAXMpuW.cjs';
3
+ import { A as ArcherOptions, a as ArchitectureSuite } from './suite-BwnPDCvU.cjs';
4
+ export { S as Selector } from './suite-BwnPDCvU.cjs';
5
+
6
+ interface ArchitectureCheckOptions {
7
+ readonly baseline?: string;
8
+ readonly config?: string;
9
+ readonly cwd?: string;
10
+ readonly failOnWarnings?: boolean;
11
+ readonly output?: string;
12
+ readonly reporter?: ReporterName;
13
+ readonly updateBaseline?: boolean;
14
+ }
15
+
16
+ interface ArchTestOptions extends ArcherOptions {
17
+ readonly test?: TestOptions;
18
+ }
19
+ interface ArcherVitestOptions extends Omit<ArchitectureCheckOptions, 'cwd' | 'updateBaseline'> {
20
+ }
21
+ interface ArcherVitestPlugin {
22
+ readonly enforce: 'pre';
23
+ readonly name: 'archer:architecture-check';
24
+ readonly configResolved: (config: {
25
+ readonly root: string;
26
+ }) => Promise<void>;
27
+ }
28
+ /**
29
+ * Runs Archer once while Vitest resolves its Vite configuration.
30
+ */
31
+ declare function archer(options?: ArcherVitestOptions): ArcherVitestPlugin;
32
+ /**
33
+ * Registers an architecture test in Vitest and returns its rule builder.
34
+ */
35
+ declare function arch(name: string, options?: ArchTestOptions): ArchitectureSuite;
36
+
37
+ export { type ArchTestOptions, ArcherOptions, type ArcherVitestOptions, type ArcherVitestPlugin, arch, archer };
@@ -0,0 +1,37 @@
1
+ import { TestOptions } from 'vitest';
2
+ import { R as ReporterName } from './reporters-xh-HQneS.js';
3
+ import { A as ArcherOptions, a as ArchitectureSuite } from './suite-BwnPDCvU.js';
4
+ export { S as Selector } from './suite-BwnPDCvU.js';
5
+
6
+ interface ArchitectureCheckOptions {
7
+ readonly baseline?: string;
8
+ readonly config?: string;
9
+ readonly cwd?: string;
10
+ readonly failOnWarnings?: boolean;
11
+ readonly output?: string;
12
+ readonly reporter?: ReporterName;
13
+ readonly updateBaseline?: boolean;
14
+ }
15
+
16
+ interface ArchTestOptions extends ArcherOptions {
17
+ readonly test?: TestOptions;
18
+ }
19
+ interface ArcherVitestOptions extends Omit<ArchitectureCheckOptions, 'cwd' | 'updateBaseline'> {
20
+ }
21
+ interface ArcherVitestPlugin {
22
+ readonly enforce: 'pre';
23
+ readonly name: 'archer:architecture-check';
24
+ readonly configResolved: (config: {
25
+ readonly root: string;
26
+ }) => Promise<void>;
27
+ }
28
+ /**
29
+ * Runs Archer once while Vitest resolves its Vite configuration.
30
+ */
31
+ declare function archer(options?: ArcherVitestOptions): ArcherVitestPlugin;
32
+ /**
33
+ * Registers an architecture test in Vitest and returns its rule builder.
34
+ */
35
+ declare function arch(name: string, options?: ArchTestOptions): ArchitectureSuite;
36
+
37
+ export { type ArchTestOptions, ArcherOptions, type ArcherVitestOptions, type ArcherVitestPlugin, arch, archer };
package/dist/vitest.js ADDED
@@ -0,0 +1,139 @@
1
+ import {
2
+ applyBaseline,
3
+ createBaseline,
4
+ readBaseline,
5
+ report,
6
+ writeBaseline
7
+ } from "./chunk-ZZ6TR4PO.js";
8
+ import {
9
+ ArchitectureSuite,
10
+ analyze
11
+ } from "./chunk-2AKMBLHG.js";
12
+ import {
13
+ ArchitectureError
14
+ } from "./chunk-IDVLHNL2.js";
15
+
16
+ // src/adapters/vitest/index.ts
17
+ import { test } from "vitest";
18
+
19
+ // src/application/check.ts
20
+ import { existsSync } from "fs";
21
+ import { mkdir, writeFile } from "fs/promises";
22
+ import path from "path";
23
+ import { createJiti } from "jiti";
24
+ var CONFIG_NAMES = [
25
+ "archer.config.ts",
26
+ "archer.config.mts",
27
+ "archer.config.cts",
28
+ "archer.config.js",
29
+ "archer.config.mjs",
30
+ "archer.config.cjs"
31
+ ];
32
+ var REPORTERS = /* @__PURE__ */ new Set([
33
+ "agent",
34
+ "dot",
35
+ "github",
36
+ "json",
37
+ "llm",
38
+ "pretty",
39
+ "sarif"
40
+ ]);
41
+ function findConfig(cwd, explicit) {
42
+ if (explicit !== void 0) {
43
+ const resolved = path.resolve(cwd, explicit);
44
+ if (!existsSync(resolved)) throw new Error(`Archer config not found: ${resolved}`);
45
+ return resolved;
46
+ }
47
+ for (const name of CONFIG_NAMES) {
48
+ const candidate = path.join(cwd, name);
49
+ if (existsSync(candidate)) return candidate;
50
+ }
51
+ throw new Error(`No Archer config found in ${cwd}. Run 'archer init' to create one.`);
52
+ }
53
+ async function loadConfig(fileName) {
54
+ const jiti = createJiti(fileName, { moduleCache: false });
55
+ const config = await jiti.import(fileName, { default: true });
56
+ if (typeof config !== "object" || config === null || !("rules" in config) || typeof config.rules !== "function") {
57
+ throw new TypeError(`Archer config '${fileName}' must export defineConfig({ rules() {} }).`);
58
+ }
59
+ return config;
60
+ }
61
+ async function writeReport(fileName, content) {
62
+ await mkdir(path.dirname(fileName), { recursive: true });
63
+ await writeFile(fileName, `${content}
64
+ `, "utf8");
65
+ }
66
+ async function checkArchitecture(options = {}) {
67
+ const cwd = path.resolve(options.cwd ?? process.cwd());
68
+ const configPath = findConfig(cwd, options.config);
69
+ const config = await loadConfig(configPath);
70
+ if (config.reporter !== void 0 && !REPORTERS.has(config.reporter)) {
71
+ throw new TypeError(`Unknown reporter '${config.reporter}' in ${configPath}.`);
72
+ }
73
+ const configDirectory = path.dirname(configPath);
74
+ const root = path.resolve(configDirectory, config.root ?? ".");
75
+ const suite = new ArchitectureSuite({ ...config, root });
76
+ await config.rules(suite);
77
+ const graph = await analyze({ ...config, root });
78
+ const originalResult = await suite.check(graph);
79
+ const configuredBaseline = options.baseline ?? config.baseline;
80
+ const baselinePath = path.resolve(configDirectory, configuredBaseline ?? ".archer-baseline.json");
81
+ const updateBaseline = options.updateBaseline ?? false;
82
+ if (updateBaseline) await writeBaseline(baselinePath, createBaseline(originalResult));
83
+ if (!updateBaseline && configuredBaseline !== void 0 && !existsSync(baselinePath)) {
84
+ throw new Error(`Archer baseline not found: ${baselinePath}`);
85
+ }
86
+ const result = !updateBaseline && existsSync(baselinePath) ? applyBaseline(originalResult, await readBaseline(baselinePath)) : originalResult;
87
+ const reporter = options.reporter ?? config.reporter ?? "pretty";
88
+ const content = report(reporter, result, graph);
89
+ const configuredOutput = options.output ?? config.output;
90
+ const outputPath = configuredOutput === void 0 ? void 0 : path.resolve(configDirectory, configuredOutput);
91
+ if (outputPath !== void 0) await writeReport(outputPath, content);
92
+ return {
93
+ baselinePath,
94
+ code: result.ok && (!(options.failOnWarnings ?? false) || result.warnings === 0) ? 0 : 1,
95
+ content,
96
+ graph,
97
+ ...outputPath === void 0 ? {} : { outputPath },
98
+ result,
99
+ updatedBaseline: updateBaseline
100
+ };
101
+ }
102
+
103
+ // src/adapters/vitest/index.ts
104
+ var ArcherVitestError = class extends Error {
105
+ constructor(message) {
106
+ super(message);
107
+ this.name = "ArcherVitestError";
108
+ }
109
+ };
110
+ function archer(options = {}) {
111
+ let execution;
112
+ return {
113
+ enforce: "pre",
114
+ name: "archer:architecture-check",
115
+ configResolved(config) {
116
+ execution ??= checkArchitecture({ ...options, cwd: config.root }).then((result) => {
117
+ if (result.code !== 0) throw new ArcherVitestError(result.content);
118
+ if (result.outputPath === void 0) process.stdout.write(`${result.content}
119
+ `);
120
+ });
121
+ return execution;
122
+ }
123
+ };
124
+ }
125
+ function arch(name, options = {}) {
126
+ const { test: testOptions, ...archerOptions } = options;
127
+ const suite = new ArchitectureSuite(archerOptions);
128
+ test(name, testOptions ?? {}, async () => {
129
+ const result = await suite.check();
130
+ if (!result.ok) {
131
+ throw new ArchitectureError(result);
132
+ }
133
+ });
134
+ return suite;
135
+ }
136
+ export {
137
+ arch,
138
+ archer
139
+ };
package/package.json CHANGED
@@ -1,6 +1,218 @@
1
1
  {
2
2
  "name": "@lucasacoutinho/archer",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "Architecture tests for TypeScript and JavaScript projects",
5
+ "keywords": [
6
+ "architecture",
7
+ "architecture-testing",
8
+ "archunit",
9
+ "boundaries",
10
+ "bun",
11
+ "dependency-rules",
12
+ "javascript",
13
+ "jest",
14
+ "monorepo",
15
+ "typescript",
16
+ "vitest"
17
+ ],
18
+ "homepage": "https://github.com/lucasacoutinho/archer#readme",
19
+ "bugs": {
20
+ "url": "https://github.com/lucasacoutinho/archer/issues"
21
+ },
22
+ "license": "Apache-2.0",
23
+ "author": "Lucas Coutinho",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/lucasacoutinho/archer.git"
27
+ },
28
+ "bin": {
29
+ "archer": "dist/cli.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "skills",
34
+ "README.md",
35
+ "LICENSE",
36
+ "NOTICE"
37
+ ],
38
+ "type": "module",
39
+ "sideEffects": [
40
+ "./dist/bun/plugin.*",
41
+ "./dist/jest/plugin.*",
42
+ "./dist/vitest/plugin.*"
43
+ ],
44
+ "main": "./dist/index.cjs",
45
+ "module": "./dist/index.js",
46
+ "types": "./dist/index.d.ts",
47
+ "typesVersions": {
48
+ "*": {
49
+ "vitest": [
50
+ "dist/vitest.d.ts"
51
+ ],
52
+ "vitest/plugin": [
53
+ "dist/vitest/plugin.d.ts"
54
+ ],
55
+ "jest": [
56
+ "dist/jest.d.ts"
57
+ ],
58
+ "jest/plugin": [
59
+ "dist/jest/plugin.d.ts"
60
+ ],
61
+ "bun": [
62
+ "dist/bun.d.ts"
63
+ ],
64
+ "bun/plugin": [
65
+ "dist/bun/plugin.d.ts"
66
+ ],
67
+ "node": [
68
+ "dist/node.d.ts"
69
+ ],
70
+ "*": [
71
+ "dist/index.d.ts"
72
+ ]
73
+ }
74
+ },
75
+ "exports": {
76
+ ".": {
77
+ "import": {
78
+ "types": "./dist/index.d.ts",
79
+ "default": "./dist/index.js"
80
+ },
81
+ "require": {
82
+ "types": "./dist/index.d.cts",
83
+ "default": "./dist/index.cjs"
84
+ }
85
+ },
86
+ "./vitest": {
87
+ "import": {
88
+ "types": "./dist/vitest.d.ts",
89
+ "default": "./dist/vitest.js"
90
+ },
91
+ "require": {
92
+ "types": "./dist/vitest.d.cts",
93
+ "default": "./dist/vitest.cjs"
94
+ }
95
+ },
96
+ "./vitest/plugin": {
97
+ "import": {
98
+ "types": "./dist/vitest/plugin.d.ts",
99
+ "default": "./dist/vitest/plugin.js"
100
+ },
101
+ "require": {
102
+ "types": "./dist/vitest/plugin.d.cts",
103
+ "default": "./dist/vitest/plugin.cjs"
104
+ }
105
+ },
106
+ "./jest": {
107
+ "import": {
108
+ "types": "./dist/jest.d.ts",
109
+ "default": "./dist/jest.js"
110
+ },
111
+ "require": {
112
+ "types": "./dist/jest.d.cts",
113
+ "default": "./dist/jest.cjs"
114
+ }
115
+ },
116
+ "./jest/plugin": {
117
+ "import": {
118
+ "types": "./dist/jest/plugin.d.ts",
119
+ "default": "./dist/jest/plugin.js"
120
+ },
121
+ "require": {
122
+ "types": "./dist/jest/plugin.d.cts",
123
+ "default": "./dist/jest/plugin.cjs"
124
+ }
125
+ },
126
+ "./bun": {
127
+ "import": {
128
+ "types": "./dist/bun.d.ts",
129
+ "default": "./dist/bun.js"
130
+ },
131
+ "require": {
132
+ "types": "./dist/bun.d.cts",
133
+ "default": "./dist/bun.cjs"
134
+ }
135
+ },
136
+ "./bun/plugin": {
137
+ "import": {
138
+ "types": "./dist/bun/plugin.d.ts",
139
+ "default": "./dist/bun/plugin.js"
140
+ },
141
+ "require": {
142
+ "types": "./dist/bun/plugin.d.cts",
143
+ "default": "./dist/bun/plugin.cjs"
144
+ }
145
+ },
146
+ "./node": {
147
+ "import": {
148
+ "types": "./dist/node.d.ts",
149
+ "default": "./dist/node.js"
150
+ },
151
+ "require": {
152
+ "types": "./dist/node.d.cts",
153
+ "default": "./dist/node.cjs"
154
+ }
155
+ }
156
+ },
157
+ "publishConfig": {
158
+ "access": "public"
159
+ },
160
+ "scripts": {
161
+ "build": "tsup",
162
+ "check": "npm run fmt:check && npm run lint && npm run typecheck && npm test && npm run test:adapters && npm run build",
163
+ "check:package": "npm run build && publint && attw --pack .",
164
+ "fmt": "oxfmt --write .",
165
+ "fmt:check": "oxfmt --check .",
166
+ "lint": "oxlint",
167
+ "lint:fix": "oxlint --fix",
168
+ "prepublishOnly": "npm run check",
169
+ "release:check": "npm run check && npm run check:package && npm audit --omit=dev && npm pack --dry-run",
170
+ "test": "vitest run",
171
+ "test:adapters": "npm run build && npm run test:jest && npm run test:node && npm run test:bun && npm run test:cli && npm run test:dogfood",
172
+ "test:bun": "bun test test/runners/bun-adapter.test.mjs",
173
+ "test:cli": "node --test test/runners/cli.test.mjs",
174
+ "test:dogfood": "node dist/cli.js check --config archer.config.ts",
175
+ "test:jest": "jest --runInBand --runTestsByPath test/runners/jest-adapter.test.cjs",
176
+ "test:node": "node --test test/runners/node-adapter.test.mjs",
177
+ "test:tia": "vitest run --config vitest.tia.config.ts",
178
+ "test:tia:verify": "node config/run-tiacore.mjs verify --output .tia/verification.json -- npm run test:tia",
179
+ "typecheck": "tsc --noEmit"
180
+ },
181
+ "dependencies": {
182
+ "chokidar": "^5.0.0",
183
+ "commander": "^14.0.3",
184
+ "jiti": "^2.7.0",
185
+ "picomatch": "^4.0.3",
186
+ "tinyglobby": "^0.2.15",
187
+ "typescript": "^5.9.0"
188
+ },
189
+ "devDependencies": {
190
+ "@arethetypeswrong/cli": "^0.18.2",
191
+ "@jest/globals": "^30.4.1",
192
+ "@types/bun": "^1.3.14",
193
+ "@types/node": "^24.0.0",
194
+ "@types/picomatch": "^4.0.2",
195
+ "jest": "^30.4.2",
196
+ "oxfmt": "^0.61.0",
197
+ "oxlint": "^1.76.0",
198
+ "publint": "^0.3.12",
199
+ "tsup": "^8.5.0",
200
+ "typescript": "^5.9.0",
201
+ "vitest": "^4.0.0"
202
+ },
203
+ "peerDependencies": {
204
+ "@jest/globals": ">=29.0.0 <31.0.0",
205
+ "vitest": ">=3.0.0 <5.0.0"
206
+ },
207
+ "peerDependenciesMeta": {
208
+ "@jest/globals": {
209
+ "optional": true
210
+ },
211
+ "vitest": {
212
+ "optional": true
213
+ }
214
+ },
215
+ "engines": {
216
+ "node": ">=20.0.0"
217
+ }
218
+ }
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: archer
3
+ description: Add, configure, run, and repair Archer architecture tests for JavaScript and TypeScript projects. Use when an agent needs to enforce module boundaries, named layers, dependency direction, public entrypoints, cycles, declaration conventions, or import rules; integrate Archer with its CLI, Vitest, Jest, Bun, or node:test; or interpret and fix Archer violations.
4
+ ---
5
+
6
+ # Archer
7
+
8
+ Use Archer to make architectural constraints executable. Prefer rules that describe an existing design decision and produce a specific source change when they fail.
9
+
10
+ ## Inspect the project
11
+
12
+ 1. Read `package.json`, workspace configuration, and relevant `tsconfig` files.
13
+ 2. Find existing `archer.config.*` and architecture tests before adding another entrypoint.
14
+ 3. Identify real dependency directions from imports. Do not infer layers from folder names alone when the code says otherwise.
15
+ 4. Read the installed Archer README or declarations when its version may differ from this skill.
16
+
17
+ ## Set up Archer
18
+
19
+ Install `@lucasacoutinho/archer` as a development dependency with the repository's package manager. Prefer the CLI for a runner-independent check:
20
+
21
+ ```ts
22
+ import { defineConfig } from '@lucasacoutinho/archer'
23
+
24
+ export default defineConfig({
25
+ rules(archer) {
26
+ archer
27
+ .expect('src/domain/**')
28
+ .not.toDependOn('src/infrastructure/**')
29
+ .as('boundaries/domain-isolation')
30
+ .because('Domain code must remain independent of adapters.')
31
+
32
+ archer.expect('src/**').toBeFreeOfCycles().as('dependencies/no-cycles')
33
+ },
34
+ })
35
+ ```
36
+
37
+ Run the locally installed binary. With npm, use `npx --no-install archer check`; use the package manager's equivalent local-exec command elsewhere. Add that command to CI after it passes locally.
38
+
39
+ Use `arch()` when the check should be registered as a test:
40
+
41
+ ```ts
42
+ import { arch } from '@lucasacoutinho/archer/vitest'
43
+
44
+ arch('domain boundaries').expect('src/domain/**').not.toDependOn('src/infrastructure/**')
45
+ ```
46
+
47
+ Replace `vitest` with `jest`, `bun`, or `node` for another adapter.
48
+
49
+ Use the matcher plugin when a project prefers `expect` assertions. Add `@lucasacoutinho/archer/vitest/plugin` to Vitest `setupFiles`, `@lucasacoutinho/archer/jest/plugin` to Jest `setupFilesAfterEnv`, or `@lucasacoutinho/archer/bun/plugin` to Bun's test `preload`. Then await `expect(archer).toRespectArchitecture()` in a dedicated architecture test.
50
+
51
+ ## Write rules
52
+
53
+ - Start with high-confidence boundaries, cycles, and public entrypoints.
54
+ - Give durable rules an ID with `.as()` and record the reason with `.because()`.
55
+ - Use `.localDependencies()` for layer allow-lists so npm and platform modules do not need repetition.
56
+ - Use dependency-kind modifiers when type-only, runtime, or dynamic coupling has different meaning.
57
+ - Treat `dynamic:unknown` as unproven. Do not allow it just to make a check pass.
58
+ - Avoid broad ignores. Narrow an exception to the smallest selector and explain it.
59
+
60
+ Read [references/rules.md](references/rules.md) for selectors, dependency modifiers, named layers, semantic expectations, and presets.
61
+
62
+ ## Diagnose and fix findings
63
+
64
+ Run:
65
+
66
+ ```sh
67
+ npx --no-install archer check --reporter agent
68
+ ```
69
+
70
+ The first line contains the status and counts. Each remaining line is one finding with location, severity, rule ID, message, and any dependency, source, reason, or import chain.
71
+
72
+ 1. Open the reported file and follow the import chain when present.
73
+ 2. Fix the source dependency or declaration that violates the stated rule.
74
+ 3. Re-run the narrow Archer command, then the repository's normal checks.
75
+ 4. Do not weaken a rule, add an ignore, or update the baseline unless the user explicitly approves that policy change.
76
+
77
+ Use `--update-baseline` only for deliberate staged adoption in an existing codebase. Commit the baseline when the project treats it as policy.
78
+
79
+ ## Report the result
80
+
81
+ State which rules or files changed and include the final Archer summary. Separate source fixes from rule-policy changes. If computed imports, runtime injection, or generated code prevent static proof, name that limitation instead of claiming the boundary passed.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: 'Archer architecture tests'
3
+ short_description: 'Add and enforce TypeScript architecture rules'
4
+ default_prompt: 'Use $archer to add architecture tests to this TypeScript project and fix any reported violations.'
@@ -0,0 +1,87 @@
1
+ # Archer rule reference
2
+
3
+ ## Selectors
4
+
5
+ Use project-relative globs for source files. Dependency selectors also accept:
6
+
7
+ | Selector | Matches |
8
+ | --------------- | ---------------------------------------------------- |
9
+ | `src/domain/**` | Resolved local files |
10
+ | `npm:react` | A package and its subpaths |
11
+ | `node:fs` | A Node built-in |
12
+ | `bun:test` | A Bun built-in |
13
+ | `unresolved:*` | Imports TypeScript cannot resolve |
14
+ | `dynamic:*` | Computed imports or requires without a static target |
15
+
16
+ Archer resolves aliases, project references, workspace packages, re-exports, type imports, CommonJS requires, and literal dynamic imports.
17
+
18
+ ## Dependency rules
19
+
20
+ ```ts
21
+ archer
22
+ .expect('src/application/**')
23
+ .toDependOn('src/domain/**')
24
+ .not.toDependOn('src/web/**')
25
+ .toOnlyDependOn(['src/application/**', 'src/domain/**'])
26
+ .toBeDependedOnBy('src/http/**')
27
+ .toOnlyBeDependedOnBy(['src/http/**', 'src/jobs/**'])
28
+ ```
29
+
30
+ Aliases include `toUse`, `toOnlyUse`, `toUseNothing`, `toBeUsed`, `toBeUsedIn`, and `toOnlyBeUsedIn`.
31
+
32
+ Apply a modifier to the next dependency rule:
33
+
34
+ ```ts
35
+ archer
36
+ .expect('src/application/**')
37
+ .typeDependencies()
38
+ .toOnlyDependOn('src/contracts/**')
39
+ .runtimeDependencies()
40
+ .not.toDependOn('src/testing/**')
41
+ .dynamicDependencies()
42
+ .not.toDependOn('src/admin/**')
43
+ .localDependencies()
44
+ .toOnlyDependOn(['src/application/**', 'src/domain/**'])
45
+ .toTransitivelyDependOn('src/domain/events.ts')
46
+ ```
47
+
48
+ Use `includingDependencyKinds(...)`, `transitively()`, and `toTransitivelyBeDependedOnBy(...)` for other combinations.
49
+
50
+ ## Layers and cycles
51
+
52
+ ```ts
53
+ const layers = archer.layers({
54
+ domain: 'src/domain/**',
55
+ application: 'src/application/**',
56
+ infrastructure: 'src/infrastructure/**',
57
+ })
58
+
59
+ layers.where('domain').mayNotDependOn('infrastructure')
60
+ layers.where('application').mayOnlyDependOn('domain')
61
+ layers.where('domain').mayOnlyBeUsedBy('application')
62
+ layers.where('domain').exposeThrough('src/domain/index.ts')
63
+ layers.toBeFreeOfCycles()
64
+
65
+ archer.expect('src/**').toBeFreeOfFolderCycles(2)
66
+ ```
67
+
68
+ ## TypeScript-aware expectations
69
+
70
+ - Kinds: `toBeClasses`, `toBeInterfaces`, `toBeEnums`, `toBeFunctions`, `toBeTypes`
71
+ - Declarations: `toBeAbstract`, `toBeExported`, `toExtend`, `toImplement`, `toHaveDecorator`
72
+ - Members: `toHaveReadonlyProperties`, `toHaveMethods`, `toHavePublicMethods`, `toHaveProtectedMethods`, `toHavePrivateMethods`, `toOnlyHavePublicMethods`
73
+ - Modules: `toOnlyExport`, `toHaveResolvableDependencies`, `toBePublicApi`
74
+ - Source: `toUseStrictEquality`, `toUseGlobals`, `toHavePrefix`, `toHaveSuffix`, `toHaveLineCountLessThan`
75
+ - Imported calls: `toUseImportedSymbols('react', 'useEffect')`; aliases and namespace calls resolve to the original module export
76
+
77
+ Negate supported rules with `.not`. Remove selected files with `.ignoring(...)`. Attach `.as(id)`, `.because(reason)`, and `.withSeverity('warning' | 'info' | 'error')` after a rule.
78
+
79
+ ## Presets and custom checks
80
+
81
+ Built-in presets are `strict`, `node`, and `security`:
82
+
83
+ ```ts
84
+ archer.preset('strict')
85
+ ```
86
+
87
+ Use `toSatisfy(id, evaluator)` for selector-scoped custom rules or `archer.rule(id, evaluator)` for a graph-wide rule. Return structured `ArchitectureViolation` objects. Prefer built-in rules when they express the same constraint.