@ecoma-io/archkeep 0.14.0 → 0.16.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 +11 -5
- package/cli.mjs +571 -61
- package/commands.mjs +57 -0
- package/lsp.mjs +15 -2
- package/package.json +8 -2
- package/src/analysis/analyze.mjs +15 -0
- package/src/analysis/contract.md +36 -18
- package/src/analysis/csharp.mjs +485 -0
- package/src/analysis/dotnet/csproj.mjs +380 -0
- package/src/analysis/dotnet/mask.mjs +178 -0
- package/src/analysis/dotnet/namespaces.mjs +172 -0
- package/src/analysis/dotnet/resolve.mjs +89 -0
- package/src/analysis/go.mjs +289 -5
- package/src/analysis/java.mjs +329 -0
- package/src/analysis/jvm/gradle.mjs +545 -0
- package/src/analysis/jvm/mask.mjs +170 -0
- package/src/analysis/jvm/maven.mjs +612 -0
- package/src/analysis/jvm/packages.mjs +209 -0
- package/src/analysis/jvm/resolve.mjs +139 -0
- package/src/analysis/kotlin.mjs +210 -0
- package/src/analysis/manifest-util.mjs +30 -0
- package/src/analysis/python.mjs +3 -2
- package/src/analysis/registry.mjs +11 -0
- package/src/analysis/rust.mjs +171 -17
- package/src/analysis/source-util.mjs +155 -6
- package/src/analysis/typescript.mjs +11 -3
- package/src/commands/README.md +52 -1
- package/src/commands/change-intent.mjs +461 -0
- package/src/commands/change.mjs +612 -0
- package/src/commands/check.mjs +2 -1
- package/src/commands/context.mjs +124 -16
- package/src/commands/custom-rules.mjs +286 -2
- package/src/commands/delta-classify.mjs +195 -33
- package/src/commands/delta-snapshot.mjs +156 -1
- package/src/commands/delta.mjs +142 -17
- package/src/commands/diff.mjs +41 -13
- package/src/commands/evolution.mjs +473 -0
- package/src/commands/history.mjs +130 -103
- package/src/commands/policy.mjs +57 -0
- package/src/commands/provenance.mjs +7 -44
- package/src/commands/rules.mjs +775 -0
- package/src/commands/trajectory.mjs +437 -0
- package/src/governance/profile-registry.mjs +0 -1
- package/src/graph/create-dependencies.mjs +138 -15
- package/src/lsp/diagnose.mjs +1 -1
- package/src/lsp/server.mjs +97 -1
- package/src/lsp/workspace-index.mjs +106 -15
- package/src/options.mjs +30 -7
- package/src/path-util.mjs +40 -0
- package/src/process.mjs +10 -1
- package/src/providers/moon.mjs +287 -36
- package/src/providers/native/differential.fixtures.mjs +32 -6
- package/src/providers/native/discover.mjs +83 -4
- package/src/providers/native/graph.mjs +58 -0
- package/src/providers/native/model.mjs +59 -1
- package/src/report/change-text.mjs +148 -0
- package/src/report/delta-text.mjs +82 -1
- package/src/report/evolution-text.mjs +83 -0
- package/src/report/history-text.mjs +4 -114
- package/src/report/sarif.mjs +255 -0
- package/src/report/snapshot-text.mjs +123 -0
- package/src/report/trajectory-text.mjs +143 -0
- package/src/rules/index.mjs +21 -6
- package/src/rules/reachability.mjs +2 -0
- package/src/rules/tags.mjs +7 -5
- package/src/rules/topology.mjs +5 -3
- package/src/tsconfig-paths.mjs +3 -2
- package/src/workspace.mjs +115 -23
package/commands.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command layer's public face — what an integration imports from this
|
|
3
|
+
* package's `./commands` subpath to run the same commands `cli.mjs` runs,
|
|
4
|
+
* in-process, without spawning the CLI or parsing its text output.
|
|
5
|
+
*
|
|
6
|
+
* It holds no logic on purpose, the same bargain `./nx` (`nx.mjs`) makes: a
|
|
7
|
+
* named entry that is a re-export, so the command layer can grow under
|
|
8
|
+
* `src/commands/` without a second copy of any decision appearing beside it.
|
|
9
|
+
* `cli.mjs` and this file are two faces of one layer — the CLI owns argv,
|
|
10
|
+
* output destinations and exit codes; an importer of this face owns those for
|
|
11
|
+
* itself, and gets back exactly what the command functions return: statuses,
|
|
12
|
+
* payloads and reports, never a printed byte.
|
|
13
|
+
*
|
|
14
|
+
* Who this is for: integrations that compose the CLI's verbs programmatically
|
|
15
|
+
* — `packages/archkeep-mcp`, the agent capability interface, is the first. The
|
|
16
|
+
* engine primitives (discovery and judgment) remain the root entry
|
|
17
|
+
* (`index.mjs`); a caller that wants to JUDGE import records itself composes
|
|
18
|
+
* those, while a caller that wants the CLI's answers — a verdict, an impact
|
|
19
|
+
* set, an explanation — composes these. The two entries never overlap: a
|
|
20
|
+
* function exported here is never also exported there, so no integration can
|
|
21
|
+
* accidentally hold two spellings of one decision.
|
|
22
|
+
*
|
|
23
|
+
* The roster is exactly what the first consumer composes, no more — a
|
|
24
|
+
* function joins it when an integration calls it, never in anticipation of
|
|
25
|
+
* one. What is deliberately absent: the `run*` drivers and argv parsing
|
|
26
|
+
* (`cli.mjs` alone owns what a process's stdout and exit code mean), and the
|
|
27
|
+
* renderers (`src/report/` shapes bytes for humans; an importer reads the
|
|
28
|
+
* envelopes the commands already return).
|
|
29
|
+
*
|
|
30
|
+
* The seams the CLI threads are threaded the same way here: `readGraph` and
|
|
31
|
+
* `listFiles` are injectable on every entry point that reaches outside the
|
|
32
|
+
* process, so a caller drives the real analysis, rules and reports over a
|
|
33
|
+
* fixture tree with neither Nx nor git present.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export { resolveCommandContext, WORKSPACE_MARKERS } from "./src/commands/context.mjs";
|
|
37
|
+
|
|
38
|
+
export { resolveDescribedPolicy, resolvePolicy } from "./src/commands/policy.mjs";
|
|
39
|
+
|
|
40
|
+
export { check } from "./src/commands/check.mjs";
|
|
41
|
+
export { graphCommand } from "./src/commands/graph.mjs";
|
|
42
|
+
export { impactCommand } from "./src/commands/impact.mjs";
|
|
43
|
+
export { explainCommand } from "./src/commands/explain.mjs";
|
|
44
|
+
export { driftCommand } from "./src/commands/drift.mjs";
|
|
45
|
+
export { planContextCommand } from "./src/commands/plan-context-command.mjs";
|
|
46
|
+
export { historyCommand } from "./src/commands/history.mjs";
|
|
47
|
+
export { adrCommand } from "./src/commands/adr.mjs";
|
|
48
|
+
export { discoverCommand } from "./src/commands/discover.mjs";
|
|
49
|
+
export { reconcileCommand } from "./src/commands/reconcile.mjs";
|
|
50
|
+
export {
|
|
51
|
+
rulesAddCommand,
|
|
52
|
+
rulesInfoCommand,
|
|
53
|
+
rulesListCommand,
|
|
54
|
+
rulesVerifyCommand,
|
|
55
|
+
} from "./src/commands/rules.mjs";
|
|
56
|
+
|
|
57
|
+
export { UsageError } from "./src/errors.mjs";
|
package/lsp.mjs
CHANGED
|
@@ -68,11 +68,24 @@ export function serve(input = process.stdin, output = process.stdout, onExit = n
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
pending = framed.rest;
|
|
71
|
-
for (const message of framed.messages)
|
|
71
|
+
for (const message of framed.messages) {
|
|
72
|
+
// `handle` is async and owns its own error envelope — a rejection here
|
|
73
|
+
// is a wiring defect, not a diagnostic. The catch reports it on stderr
|
|
74
|
+
// (the channel a client surfaces as its server log) instead of letting
|
|
75
|
+
// it become an unhandled rejection that kills the connection, or
|
|
76
|
+
// nothing at all, which would read as a clean frame.
|
|
77
|
+
server
|
|
78
|
+
.handle(message)
|
|
79
|
+
.catch((error) => process.stderr.write(`archkeep-lsp: ${error?.message ?? error}\n`));
|
|
80
|
+
}
|
|
72
81
|
});
|
|
73
82
|
// A client that closed the pipe without saying `exit` did not shut the server
|
|
74
83
|
// down; reporting that as a clean stop would hide a crashed editor.
|
|
75
|
-
input.on("end", () =>
|
|
84
|
+
input.on("end", () => {
|
|
85
|
+
server
|
|
86
|
+
.handle({ jsonrpc: "2.0", method: "exit" })
|
|
87
|
+
.catch((error) => process.stderr.write(`archkeep-lsp: ${error?.message ?? error}\n`));
|
|
88
|
+
});
|
|
76
89
|
return server;
|
|
77
90
|
}
|
|
78
91
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecoma-io/archkeep",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Architecture enforcement for polyglot repositories — dependency graphs and module boundaries for Go, Rust, Python, TypeScript, JavaScript and
|
|
3
|
+
"version": "0.16.0",
|
|
4
|
+
"description": "Architecture enforcement for polyglot repositories — dependency graphs and module boundaries for Go, Rust, Python, TypeScript, JavaScript, Vue, Java and Kotlin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"architecture",
|
|
7
7
|
"enforcement",
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
"exports": {
|
|
29
29
|
".": "./index.mjs",
|
|
30
30
|
"./nx": "./nx.mjs",
|
|
31
|
+
"./commands": "./commands.mjs",
|
|
31
32
|
"./presets/*.json": "./presets/*.json",
|
|
32
33
|
"./package.json": "./package.json"
|
|
33
34
|
},
|
|
34
35
|
"files": [
|
|
35
36
|
"index.mjs",
|
|
36
37
|
"nx.mjs",
|
|
38
|
+
"commands.mjs",
|
|
37
39
|
"cli.mjs",
|
|
38
40
|
"lsp.mjs",
|
|
39
41
|
"src/",
|
|
@@ -51,6 +53,7 @@
|
|
|
51
53
|
},
|
|
52
54
|
"peerDependencies": {
|
|
53
55
|
"@nx/eslint-plugin": ">=21",
|
|
56
|
+
"fast-xml-parser": "5.11.0",
|
|
54
57
|
"nx": ">=21",
|
|
55
58
|
"typescript": ">=5 <7",
|
|
56
59
|
"vue": ">=3"
|
|
@@ -59,6 +62,9 @@
|
|
|
59
62
|
"@nx/eslint-plugin": {
|
|
60
63
|
"optional": true
|
|
61
64
|
},
|
|
65
|
+
"fast-xml-parser": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
62
68
|
"nx": {
|
|
63
69
|
"optional": true
|
|
64
70
|
},
|
package/src/analysis/analyze.mjs
CHANGED
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
* exists. Every other import site keeps naming them from this module.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { analyzeCSharp } from "./csharp.mjs";
|
|
25
26
|
import { analyzeGo } from "./go.mjs";
|
|
27
|
+
import { analyzeJava } from "./java.mjs";
|
|
28
|
+
import { analyzeKotlin } from "./kotlin.mjs";
|
|
26
29
|
import { analyzePython } from "./python.mjs";
|
|
27
30
|
import { LANGUAGE_BY_EXTENSION, languageOf } from "./registry.mjs";
|
|
28
31
|
import { analyzeRust } from "./rust.mjs";
|
|
@@ -78,6 +81,15 @@ export { LANGUAGE_BY_EXTENSION, languageOf };
|
|
|
78
81
|
* `./x` and `../x` in JavaScript, `crate::`/`self::`/`super::` and a sibling
|
|
79
82
|
* crate target of the same Cargo package in Rust, a leading-dot import in
|
|
80
83
|
* Python. It is the counter-evidence `noSelfCircularDependencies` looks for.
|
|
84
|
+
* @property {boolean} namesOnly The language's ONLY import spelling is a
|
|
85
|
+
* name — a Go module path, a Rust crate, a Python dotted module, a C#
|
|
86
|
+
* namespace, a Java or Kotlin package — so no specifier it produces is ever
|
|
87
|
+
* a filesystem path, and text beginning `libs/…` or `apps/…` is a name whose
|
|
88
|
+
* first segments are those words, not a path into a project. Constant on
|
|
89
|
+
* every record one analyzer produces, unlike `path` and `relative`, which
|
|
90
|
+
* answer per specifier. `isAbsoluteImportIntoAnotherProject` is gated on it
|
|
91
|
+
* (#376): false in the JavaScript family, where a bare `libs/x` is a deep
|
|
92
|
+
* import and a `/libs/x` an absolute path.
|
|
81
93
|
*/
|
|
82
94
|
|
|
83
95
|
/**
|
|
@@ -170,6 +182,9 @@ const ANALYZER_BY_LANGUAGE = Object.freeze({
|
|
|
170
182
|
go: analyzeGo,
|
|
171
183
|
rust: analyzeRust,
|
|
172
184
|
python: analyzePython,
|
|
185
|
+
java: analyzeJava,
|
|
186
|
+
kotlin: analyzeKotlin,
|
|
187
|
+
csharp: analyzeCSharp,
|
|
173
188
|
});
|
|
174
189
|
|
|
175
190
|
/**
|
package/src/analysis/contract.md
CHANGED
|
@@ -71,23 +71,40 @@ beats every consumer remembering which convention this tool chose.
|
|
|
71
71
|
### How the specifier is spelled is a per-language fact, so the analyzer states it
|
|
72
72
|
|
|
73
73
|
`specifier` is the raw text; `spelling` is what that text IS in the language it
|
|
74
|
-
was written in.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
| `
|
|
82
|
-
|
|
|
83
|
-
| `
|
|
74
|
+
was written in. Three bits, because the rules ask three independent questions —
|
|
75
|
+
the first two per specifier, the third per language and therefore constant on
|
|
76
|
+
every record one analyzer produces:
|
|
77
|
+
|
|
78
|
+
| | `path` | `relative` | `namesOnly` |
|
|
79
|
+
| --------------------------------- | :----: | :--------: | :---------: |
|
|
80
|
+
| `./x`, `../x`, `.`, `..` (JS) | yes | yes | no |
|
|
81
|
+
| `/x` (JS) | yes | no | no |
|
|
82
|
+
| `react` (JS, bare) | no | no | no |
|
|
83
|
+
| `crate::x`, `self::x`, `super::x` | no | yes | yes |
|
|
84
|
+
| `rba_desktop_lib::run` from a bin | no | yes | yes |
|
|
85
|
+
| `.mod`, `..pkg.sub` (Python) | no | yes | yes |
|
|
86
|
+
| `react`, `serde`, `example.com/m` | no | no | yes* |
|
|
87
|
+
|
|
88
|
+
\* the last row holds for `serde` and `example.com/m`; a JavaScript `react` is
|
|
89
|
+
`namesOnly: no` — the same text is a package name in one language and a name
|
|
90
|
+
that IS the only spelling in another, which is exactly why the bit rides on the
|
|
91
|
+
record instead of being derived from the text.
|
|
84
92
|
|
|
85
93
|
`path` says the specifier is a **filesystem path**: resolvable by path
|
|
86
94
|
arithmetic against the importing file, and naming no package. It decides
|
|
87
95
|
whether a specifier may receive a synthesized external node, and which message
|
|
88
96
|
an unresolvable one gets. `relative` says the specifier **reaches inside its
|
|
89
97
|
own project without going out through the project's public name** — the
|
|
90
|
-
counter-evidence `noSelfCircularDependencies` looks for.
|
|
98
|
+
counter-evidence `noSelfCircularDependencies` looks for. `namesOnly` says the
|
|
99
|
+
**language has no path spelling at all** — every specifier it produces is a
|
|
100
|
+
name, so no path-text rule applies to any of them. It gates
|
|
101
|
+
`isAbsoluteImportIntoAnotherProject`, which judges the JavaScript-family
|
|
102
|
+
spellings a bare `libs/x` deep import and a `/libs/x` absolute path: applied to
|
|
103
|
+
a language whose only spelling is the name, it read a Go `module libs/foo`'s
|
|
104
|
+
legal `import "libs/foo/bar"` as an absolute path into a project — an
|
|
105
|
+
unfixable verdict, because the name is the only spelling the language has
|
|
106
|
+
(#376). The edge such an import resolves to is still judged by every rule
|
|
107
|
+
below the spelling check; only the spelling check itself stands down.
|
|
91
108
|
|
|
92
109
|
**Why the analyzer answers and not the rules.** The rules layer used to derive
|
|
93
110
|
both from the text with one predicate — `.`, `..`, `./`, `../` — which is
|
|
@@ -101,10 +118,10 @@ import; the rules layer knows neither, and a language table there would be a
|
|
|
101
118
|
second registry drifting from `LANGUAGE_BY_EXTENSION`. So the record carries the
|
|
102
119
|
fact and the rule reads it.
|
|
103
120
|
|
|
104
|
-
The field is **mandatory**, and `evaluate()` throws on a record that omits
|
|
105
|
-
rather than falling back to the JavaScript shape. A default is how
|
|
106
|
-
analyzer inherits this bug silently; a throw is how it is told, once,
|
|
107
|
-
first record it produces.
|
|
121
|
+
The field is **mandatory**, and `evaluate()` throws on a record that omits any
|
|
122
|
+
bit of it rather than falling back to the JavaScript shape. A default is how
|
|
123
|
+
the next analyzer inherits this bug silently; a throw is how it is told, once,
|
|
124
|
+
at the first record it produces.
|
|
108
125
|
|
|
109
126
|
### Intra-project imports are emitted too
|
|
110
127
|
|
|
@@ -208,9 +225,10 @@ rule cares about, and the pressure to fix it would be an ignore list that
|
|
|
208
225
|
someone has to keep in sync with reality. An extension with no analyzer simply
|
|
209
226
|
has no imports this tool can see, which is the truth.
|
|
210
227
|
|
|
211
|
-
The registry is `LANGUAGE_BY_EXTENSION` in `
|
|
212
|
-
|
|
213
|
-
|
|
228
|
+
The registry is `LANGUAGE_BY_EXTENSION` in `registry.mjs` (re-exported by
|
|
229
|
+
`analyze.mjs`, so nothing that imports it from there has to change), and it is
|
|
230
|
+
the one place an extension is mapped. A language whose analyzer arrives adds
|
|
231
|
+
itself there and nowhere else.
|
|
214
232
|
|
|
215
233
|
## What an analyzer is handed
|
|
216
234
|
|