@ecoma-io/archkeep 0.13.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/LICENSE +202 -0
- package/README.md +262 -0
- package/cli.mjs +2792 -0
- package/index.mjs +85 -0
- package/lsp.mjs +81 -0
- package/nx.mjs +24 -0
- package/package.json +81 -0
- package/presets/clean-architecture.json +78 -0
- package/presets/ddd-bounded-contexts.json +88 -0
- package/presets/hexagonal.json +68 -0
- package/presets/layered.json +92 -0
- package/presets/modular-monolith.json +85 -0
- package/presets/vertical-slice.json +68 -0
- package/src/analysis/analyze.mjs +218 -0
- package/src/analysis/contract.md +259 -0
- package/src/analysis/go.mjs +414 -0
- package/src/analysis/manifest-util.mjs +68 -0
- package/src/analysis/python.mjs +1266 -0
- package/src/analysis/registry.mjs +74 -0
- package/src/analysis/rust.mjs +674 -0
- package/src/analysis/source-util.mjs +230 -0
- package/src/analysis/typescript.mjs +1034 -0
- package/src/analysis/vue.mjs +156 -0
- package/src/architecture-intent/intent-fingerprint.mjs +29 -0
- package/src/architecture-intent/judge.mjs +539 -0
- package/src/architecture-intent/model.mjs +703 -0
- package/src/architecture-intent/selectors.mjs +170 -0
- package/src/canonical.mjs +48 -0
- package/src/commands/README.md +266 -0
- package/src/commands/adr.mjs +248 -0
- package/src/commands/check.mjs +989 -0
- package/src/commands/context-command.mjs +212 -0
- package/src/commands/context.mjs +790 -0
- package/src/commands/custom-rules.mjs +428 -0
- package/src/commands/debt.mjs +218 -0
- package/src/commands/diff.mjs +523 -0
- package/src/commands/discover.mjs +159 -0
- package/src/commands/drift.mjs +473 -0
- package/src/commands/edge-constraints.mjs +355 -0
- package/src/commands/explain.mjs +359 -0
- package/src/commands/fitness.mjs +226 -0
- package/src/commands/graph.mjs +297 -0
- package/src/commands/health.mjs +213 -0
- package/src/commands/history.mjs +614 -0
- package/src/commands/impact.mjs +226 -0
- package/src/commands/plan-context-command.mjs +496 -0
- package/src/commands/policy.mjs +138 -0
- package/src/commands/provenance-command.mjs +352 -0
- package/src/commands/provenance.mjs +159 -0
- package/src/commands/reconcile.mjs +219 -0
- package/src/commands/report.mjs +553 -0
- package/src/commands/snapshot-meta.mjs +107 -0
- package/src/commands/waivers.mjs +240 -0
- package/src/config.mjs +1308 -0
- package/src/containment.mjs +234 -0
- package/src/custom-rules/evidence.mjs +340 -0
- package/src/custom-rules/host.mjs +1023 -0
- package/src/custom-rules/values.mjs +43 -0
- package/src/entry-point.mjs +55 -0
- package/src/errors.mjs +36 -0
- package/src/eslint-config.mjs +542 -0
- package/src/go-work.mjs +394 -0
- package/src/governance/adr-registry.mjs +539 -0
- package/src/governance/clock.mjs +69 -0
- package/src/governance/debt-ledger.mjs +274 -0
- package/src/governance/discovery-proposal.mjs +423 -0
- package/src/governance/fitness-registry.mjs +504 -0
- package/src/governance/fitness-rules.mjs +668 -0
- package/src/governance/metrics.mjs +392 -0
- package/src/governance/preset-fingerprints.json +16 -0
- package/src/governance/profile-registry.mjs +366 -0
- package/src/governance/provenance-record.mjs +177 -0
- package/src/governance/reconcile-candidates.mjs +301 -0
- package/src/governance/reconcile-score.mjs +503 -0
- package/src/governance/row-schema.mjs +208 -0
- package/src/governance/verdict.mjs +127 -0
- package/src/governance/waiver.mjs +105 -0
- package/src/graph/create-dependencies.mjs +96 -0
- package/src/intent/intent-manifest.json +347 -0
- package/src/intent/mask-non-code.mjs +640 -0
- package/src/lsp/boundary-config.mjs +225 -0
- package/src/lsp/diagnose.mjs +202 -0
- package/src/lsp/diagnostics.mjs +241 -0
- package/src/lsp/protocol.mjs +215 -0
- package/src/lsp/server.mjs +922 -0
- package/src/lsp/workspace-index.mjs +891 -0
- package/src/nx-json.mjs +95 -0
- package/src/options.mjs +611 -0
- package/src/process.mjs +91 -0
- package/src/providers/moon.mjs +733 -0
- package/src/providers/native/README.md +204 -0
- package/src/providers/native/coverage.mjs +74 -0
- package/src/providers/native/differential.fixtures.mjs +1277 -0
- package/src/providers/native/discover.mjs +431 -0
- package/src/providers/native/graph.mjs +234 -0
- package/src/providers/native/index.mjs +152 -0
- package/src/providers/native/model.mjs +755 -0
- package/src/providers/nx.mjs +178 -0
- package/src/report/README.md +89 -0
- package/src/report/adr-text.mjs +129 -0
- package/src/report/context-text.mjs +109 -0
- package/src/report/debt-text.mjs +105 -0
- package/src/report/diff-text.mjs +219 -0
- package/src/report/discover-text.mjs +186 -0
- package/src/report/drift-text.mjs +194 -0
- package/src/report/envelope-shape.mjs +161 -0
- package/src/report/evidence.mjs +157 -0
- package/src/report/explain-text.mjs +159 -0
- package/src/report/graph-text.mjs +116 -0
- package/src/report/health-text.mjs +123 -0
- package/src/report/history-text.mjs +204 -0
- package/src/report/impact-text.mjs +128 -0
- package/src/report/json.mjs +173 -0
- package/src/report/plan-context-text.mjs +159 -0
- package/src/report/provenance-text.mjs +78 -0
- package/src/report/reconcile-text.mjs +159 -0
- package/src/report/report-text.mjs +264 -0
- package/src/report/sarif.mjs +953 -0
- package/src/report/text.mjs +823 -0
- package/src/report/waivers-text.mjs +100 -0
- package/src/rules/README.md +123 -0
- package/src/rules/index.mjs +962 -0
- package/src/rules/match.mjs +1708 -0
- package/src/rules/messages.mjs +73 -0
- package/src/rules/reachability.mjs +224 -0
- package/src/rules/specifiers.mjs +300 -0
- package/src/rules/tags.mjs +238 -0
- package/src/rules/topology.mjs +333 -0
- package/src/tsconfig-paths.mjs +237 -0
- package/src/verdict.mjs +145 -0
- package/src/workspace.mjs +580 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Language Server Protocol's base layer: framing, the numeric constants the
|
|
3
|
+
* protocol fixes, and the `file:` URI conversion every other module here needs.
|
|
4
|
+
*
|
|
5
|
+
* Implemented directly rather than pulled from `vscode-languageserver`, for the
|
|
6
|
+
* reason the project `../../AGENTS.md` gives for the whole directory: this tool reaches
|
|
7
|
+
* outside Node's built-ins only where something it must AGREE with already
|
|
8
|
+
* lives there — TypeScript's own resolver, Vue's own SFC parser, Nx's own JSON
|
|
9
|
+
* reader (`./workspace-index.mjs`). The base protocol is nothing of the kind:
|
|
10
|
+
* it is a fixed external contract of about forty lines, written down in a
|
|
11
|
+
* specification, and a third-party package for it would put a dependency
|
|
12
|
+
* decision in this project that the root manifest owns.
|
|
13
|
+
*
|
|
14
|
+
* Every constant below is a value the SPECIFICATION fixes, not a workspace
|
|
15
|
+
* choice — the one class of literal that is allowed inline, and it is written in
|
|
16
|
+
* exactly one place so no consumer restates it.
|
|
17
|
+
*/
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Name and version, read from the manifest rather than repeated here. A server
|
|
23
|
+
* whose `serverInfo` disagrees with its own package is a debugging trap the
|
|
24
|
+
* first time two versions are installed side by side.
|
|
25
|
+
*
|
|
26
|
+
* The npm scope is stripped, and that is a derivation rather than a second
|
|
27
|
+
* name: `serverInfo.name` is what an editor prints in its LSP output channel
|
|
28
|
+
* and what a user greps for, while the scope is a registry namespace that says
|
|
29
|
+
* who publishes the package, not which server is running. Both binaries in
|
|
30
|
+
* `bin` are unscoped for the same reason. Deriving it keeps the version honest
|
|
31
|
+
* and the version is the half that actually matters when two are installed.
|
|
32
|
+
*/
|
|
33
|
+
const manifest = createRequire(import.meta.url)("../../package.json");
|
|
34
|
+
|
|
35
|
+
/** What `initialize` answers when a client asks who it is talking to. */
|
|
36
|
+
export const SERVER_INFO = Object.freeze({
|
|
37
|
+
name: manifest.name.replace(/^@[^/]+\//u, ""),
|
|
38
|
+
version: manifest.version,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* JSON-RPC 2.0 and LSP error codes, at the values both specifications fix.
|
|
43
|
+
*
|
|
44
|
+
* `serverNotInitialized` and `requestFailed` are LSP's own additions in the
|
|
45
|
+
* reserved JSON-RPC range; the rest are JSON-RPC's.
|
|
46
|
+
*/
|
|
47
|
+
export const ERROR_CODES = Object.freeze({
|
|
48
|
+
parseError: -32700,
|
|
49
|
+
invalidRequest: -32600,
|
|
50
|
+
methodNotFound: -32601,
|
|
51
|
+
invalidParams: -32602,
|
|
52
|
+
internalError: -32603,
|
|
53
|
+
serverNotInitialized: -32002,
|
|
54
|
+
requestFailed: -32803,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `TextDocumentSyncKind`. This server advertises `full`: the client re-sends
|
|
59
|
+
* the whole document on every change, and the server re-analyzes it from
|
|
60
|
+
* scratch. Incremental sync would mean maintaining a rope and applying ranged
|
|
61
|
+
* edits, and a single mis-applied edit makes every position in every subsequent
|
|
62
|
+
* diagnostic point at the wrong line — which is the failure this tool is least
|
|
63
|
+
* allowed to have. It stays `full` until incremental can be proven correct.
|
|
64
|
+
*/
|
|
65
|
+
export const TEXT_DOCUMENT_SYNC_KIND = Object.freeze({
|
|
66
|
+
none: 0,
|
|
67
|
+
full: 1,
|
|
68
|
+
incremental: 2,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/** LSP `DiagnosticSeverity`. */
|
|
72
|
+
export const DIAGNOSTIC_SEVERITY = Object.freeze({
|
|
73
|
+
error: 1,
|
|
74
|
+
warning: 2,
|
|
75
|
+
information: 3,
|
|
76
|
+
hint: 4,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The largest `Content-Length` this server accepts.
|
|
81
|
+
*
|
|
82
|
+
* The base protocol places no upper bound on a frame's declared body size, and
|
|
83
|
+
* an unbounded one is a denial handle on the session: a header declaring
|
|
84
|
+
* `Content-Length: 99999999999` never completes, so `frameMessages` returns
|
|
85
|
+
* `{ messages: [], rest: <everything> }` forever and every following frame —
|
|
86
|
+
* `initialize` included — is swallowed behind it. The server holds the whole
|
|
87
|
+
* `pending` buffer too, so a 1-byte trickle drives O(n²) copying. One
|
|
88
|
+
* `\r\n\r\n` followed by an implausible length is indistinguishable in the
|
|
89
|
+
* stream from a client that died mid-frame, and the server must pick one
|
|
90
|
+
* loudly (a terminal protocol error, `../../lsp.mjs`), never a silent
|
|
91
|
+
* hold-open.
|
|
92
|
+
*
|
|
93
|
+
* The cap is deliberately generous: no real LSP frame from any editor comes
|
|
94
|
+
* near it, so nothing a working client sends is refused. A frame that needs
|
|
95
|
+
* more is not a supported conversation, and saying so beats waiting for bytes
|
|
96
|
+
* that will never arrive.
|
|
97
|
+
*/
|
|
98
|
+
export const MAX_CONTENT_LENGTH = 64 * 1024 * 1024;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* LSP `MessageType`, as `window/showMessage` reports it.
|
|
102
|
+
*
|
|
103
|
+
* `error` is the one this server uses — and the one value the
|
|
104
|
+
* `window/showMessage` notification was built for: a session whose options
|
|
105
|
+
* could not be read must say so even when no document is open yet, because an
|
|
106
|
+
* editor pane that stays silent through the whole session is
|
|
107
|
+
* indistinguishable from a session that never failed.
|
|
108
|
+
*/
|
|
109
|
+
export const MESSAGE_TYPE = Object.freeze({
|
|
110
|
+
error: 1,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Splits a byte stream into LSP messages. Returns the messages it could frame
|
|
115
|
+
* plus whatever tail is not yet a whole message, which the caller feeds back
|
|
116
|
+
* in with the next chunk — or, when a header declared an implausible body size,
|
|
117
|
+
* a TERMINAL protocol error: the stream is poisoned and the caller must close
|
|
118
|
+
* the session loudly, never feed `rest` back on. When `protocolError` is set,
|
|
119
|
+
* `rest` is deliberately empty and `messages` empty too: nothing the caller
|
|
120
|
+
* could do with either would be correct, so neither carries a usable value.
|
|
121
|
+
* That a caller OTHER than `../../lsp.mjs` must read this and refuse to
|
|
122
|
+
* continue is the handshake, and it lives here rather than in the caller so no
|
|
123
|
+
* future consumer can treat the error as a recoverable pause.
|
|
124
|
+
*
|
|
125
|
+
* `Content-Length` counts BYTES, not characters, so the buffer is sliced
|
|
126
|
+
* before it is decoded — measuring a decoded string would mis-split any
|
|
127
|
+
* message containing a non-ASCII path.
|
|
128
|
+
*
|
|
129
|
+
* @param {Buffer} buffer
|
|
130
|
+
* @returns {{ messages: object[], rest: Buffer, protocolError?: string }}
|
|
131
|
+
*/
|
|
132
|
+
export function frameMessages(buffer) {
|
|
133
|
+
const messages = [];
|
|
134
|
+
let rest = buffer;
|
|
135
|
+
for (;;) {
|
|
136
|
+
const headerEnd = rest.indexOf("\r\n\r\n");
|
|
137
|
+
if (headerEnd === -1) break;
|
|
138
|
+
const headers = rest.subarray(0, headerEnd).toString("ascii");
|
|
139
|
+
const length = Number(/content-length:\s*(\d+)/i.exec(headers)?.[1]);
|
|
140
|
+
if (!Number.isInteger(length)) {
|
|
141
|
+
// A frame with no usable Content-Length cannot be skipped safely — the
|
|
142
|
+
// stream position of the next one is unknowable. Drop the header and
|
|
143
|
+
// resynchronise on the next boundary rather than guess a length.
|
|
144
|
+
rest = rest.subarray(headerEnd + 4);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (length > MAX_CONTENT_LENGTH) {
|
|
148
|
+
// A declared body larger than the server's budget is not a partial frame
|
|
149
|
+
// arriving — it is a stream that will never complete (or a peer trying
|
|
150
|
+
// to hold it open). Return a terminal error instead of `{rest: …}` so
|
|
151
|
+
// the caller closes the session loudly rather than buffering forever.
|
|
152
|
+
// The framing is still in sync: the header was parsed, the body is
|
|
153
|
+
// simply not coming, and no later frame can be framed behind it.
|
|
154
|
+
return {
|
|
155
|
+
messages: [],
|
|
156
|
+
rest: Buffer.alloc(0),
|
|
157
|
+
protocolError: `archkeep: Content-Length ${length} exceeds the ${MAX_CONTENT_LENGTH}-byte maximum; closing the session rather than waiting for a body that will never arrive`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const bodyStart = headerEnd + 4;
|
|
161
|
+
if (rest.length < bodyStart + length) break; // body still arriving
|
|
162
|
+
const body = rest.subarray(bodyStart, bodyStart + length).toString("utf8");
|
|
163
|
+
rest = rest.subarray(bodyStart + length);
|
|
164
|
+
try {
|
|
165
|
+
messages.push(JSON.parse(body));
|
|
166
|
+
} catch {
|
|
167
|
+
// Unparsable body, correctly framed: the stream stays in sync, so the
|
|
168
|
+
// session continues. Nothing is owed in reply — the id is unknown.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { messages, rest };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Encodes a message with its base-protocol header.
|
|
176
|
+
*
|
|
177
|
+
* @param {object} message
|
|
178
|
+
* @returns {Buffer}
|
|
179
|
+
*/
|
|
180
|
+
export function encodeMessage(message) {
|
|
181
|
+
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
182
|
+
return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "ascii"), body]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The filesystem path a `file:` URI names, or `null` for anything else.
|
|
187
|
+
*
|
|
188
|
+
* `null` rather than a throw, and rather than treating the URI as a path: an
|
|
189
|
+
* editor can open an untitled buffer (`untitled:Untitled-1`) or a virtual
|
|
190
|
+
* document from another extension, and the server has no file to analyze for
|
|
191
|
+
* either. The caller decides what to say about it — this function's job is to
|
|
192
|
+
* refuse to invent a path.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} uri
|
|
195
|
+
* @returns {string|null}
|
|
196
|
+
*/
|
|
197
|
+
export function uriToPath(uri) {
|
|
198
|
+
if (typeof uri !== "string" || !uri.startsWith("file:")) return null;
|
|
199
|
+
try {
|
|
200
|
+
return fileURLToPath(uri);
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The `file:` URI for an absolute path, percent-encoded as the protocol
|
|
208
|
+
* requires. Round-trips with `uriToPath`.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} path
|
|
211
|
+
* @returns {string}
|
|
212
|
+
*/
|
|
213
|
+
export function pathToUri(path) {
|
|
214
|
+
return pathToFileURL(path).href;
|
|
215
|
+
}
|