@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.
Files changed (131) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +262 -0
  3. package/cli.mjs +2792 -0
  4. package/index.mjs +85 -0
  5. package/lsp.mjs +81 -0
  6. package/nx.mjs +24 -0
  7. package/package.json +81 -0
  8. package/presets/clean-architecture.json +78 -0
  9. package/presets/ddd-bounded-contexts.json +88 -0
  10. package/presets/hexagonal.json +68 -0
  11. package/presets/layered.json +92 -0
  12. package/presets/modular-monolith.json +85 -0
  13. package/presets/vertical-slice.json +68 -0
  14. package/src/analysis/analyze.mjs +218 -0
  15. package/src/analysis/contract.md +259 -0
  16. package/src/analysis/go.mjs +414 -0
  17. package/src/analysis/manifest-util.mjs +68 -0
  18. package/src/analysis/python.mjs +1266 -0
  19. package/src/analysis/registry.mjs +74 -0
  20. package/src/analysis/rust.mjs +674 -0
  21. package/src/analysis/source-util.mjs +230 -0
  22. package/src/analysis/typescript.mjs +1034 -0
  23. package/src/analysis/vue.mjs +156 -0
  24. package/src/architecture-intent/intent-fingerprint.mjs +29 -0
  25. package/src/architecture-intent/judge.mjs +539 -0
  26. package/src/architecture-intent/model.mjs +703 -0
  27. package/src/architecture-intent/selectors.mjs +170 -0
  28. package/src/canonical.mjs +48 -0
  29. package/src/commands/README.md +266 -0
  30. package/src/commands/adr.mjs +248 -0
  31. package/src/commands/check.mjs +989 -0
  32. package/src/commands/context-command.mjs +212 -0
  33. package/src/commands/context.mjs +790 -0
  34. package/src/commands/custom-rules.mjs +428 -0
  35. package/src/commands/debt.mjs +218 -0
  36. package/src/commands/diff.mjs +523 -0
  37. package/src/commands/discover.mjs +159 -0
  38. package/src/commands/drift.mjs +473 -0
  39. package/src/commands/edge-constraints.mjs +355 -0
  40. package/src/commands/explain.mjs +359 -0
  41. package/src/commands/fitness.mjs +226 -0
  42. package/src/commands/graph.mjs +297 -0
  43. package/src/commands/health.mjs +213 -0
  44. package/src/commands/history.mjs +614 -0
  45. package/src/commands/impact.mjs +226 -0
  46. package/src/commands/plan-context-command.mjs +496 -0
  47. package/src/commands/policy.mjs +138 -0
  48. package/src/commands/provenance-command.mjs +352 -0
  49. package/src/commands/provenance.mjs +159 -0
  50. package/src/commands/reconcile.mjs +219 -0
  51. package/src/commands/report.mjs +553 -0
  52. package/src/commands/snapshot-meta.mjs +107 -0
  53. package/src/commands/waivers.mjs +240 -0
  54. package/src/config.mjs +1308 -0
  55. package/src/containment.mjs +234 -0
  56. package/src/custom-rules/evidence.mjs +340 -0
  57. package/src/custom-rules/host.mjs +1023 -0
  58. package/src/custom-rules/values.mjs +43 -0
  59. package/src/entry-point.mjs +55 -0
  60. package/src/errors.mjs +36 -0
  61. package/src/eslint-config.mjs +542 -0
  62. package/src/go-work.mjs +394 -0
  63. package/src/governance/adr-registry.mjs +539 -0
  64. package/src/governance/clock.mjs +69 -0
  65. package/src/governance/debt-ledger.mjs +274 -0
  66. package/src/governance/discovery-proposal.mjs +423 -0
  67. package/src/governance/fitness-registry.mjs +504 -0
  68. package/src/governance/fitness-rules.mjs +668 -0
  69. package/src/governance/metrics.mjs +392 -0
  70. package/src/governance/preset-fingerprints.json +16 -0
  71. package/src/governance/profile-registry.mjs +366 -0
  72. package/src/governance/provenance-record.mjs +177 -0
  73. package/src/governance/reconcile-candidates.mjs +301 -0
  74. package/src/governance/reconcile-score.mjs +503 -0
  75. package/src/governance/row-schema.mjs +208 -0
  76. package/src/governance/verdict.mjs +127 -0
  77. package/src/governance/waiver.mjs +105 -0
  78. package/src/graph/create-dependencies.mjs +96 -0
  79. package/src/intent/intent-manifest.json +347 -0
  80. package/src/intent/mask-non-code.mjs +640 -0
  81. package/src/lsp/boundary-config.mjs +225 -0
  82. package/src/lsp/diagnose.mjs +202 -0
  83. package/src/lsp/diagnostics.mjs +241 -0
  84. package/src/lsp/protocol.mjs +215 -0
  85. package/src/lsp/server.mjs +922 -0
  86. package/src/lsp/workspace-index.mjs +891 -0
  87. package/src/nx-json.mjs +95 -0
  88. package/src/options.mjs +611 -0
  89. package/src/process.mjs +91 -0
  90. package/src/providers/moon.mjs +733 -0
  91. package/src/providers/native/README.md +204 -0
  92. package/src/providers/native/coverage.mjs +74 -0
  93. package/src/providers/native/differential.fixtures.mjs +1277 -0
  94. package/src/providers/native/discover.mjs +431 -0
  95. package/src/providers/native/graph.mjs +234 -0
  96. package/src/providers/native/index.mjs +152 -0
  97. package/src/providers/native/model.mjs +755 -0
  98. package/src/providers/nx.mjs +178 -0
  99. package/src/report/README.md +89 -0
  100. package/src/report/adr-text.mjs +129 -0
  101. package/src/report/context-text.mjs +109 -0
  102. package/src/report/debt-text.mjs +105 -0
  103. package/src/report/diff-text.mjs +219 -0
  104. package/src/report/discover-text.mjs +186 -0
  105. package/src/report/drift-text.mjs +194 -0
  106. package/src/report/envelope-shape.mjs +161 -0
  107. package/src/report/evidence.mjs +157 -0
  108. package/src/report/explain-text.mjs +159 -0
  109. package/src/report/graph-text.mjs +116 -0
  110. package/src/report/health-text.mjs +123 -0
  111. package/src/report/history-text.mjs +204 -0
  112. package/src/report/impact-text.mjs +128 -0
  113. package/src/report/json.mjs +173 -0
  114. package/src/report/plan-context-text.mjs +159 -0
  115. package/src/report/provenance-text.mjs +78 -0
  116. package/src/report/reconcile-text.mjs +159 -0
  117. package/src/report/report-text.mjs +264 -0
  118. package/src/report/sarif.mjs +953 -0
  119. package/src/report/text.mjs +823 -0
  120. package/src/report/waivers-text.mjs +100 -0
  121. package/src/rules/README.md +123 -0
  122. package/src/rules/index.mjs +962 -0
  123. package/src/rules/match.mjs +1708 -0
  124. package/src/rules/messages.mjs +73 -0
  125. package/src/rules/reachability.mjs +224 -0
  126. package/src/rules/specifiers.mjs +300 -0
  127. package/src/rules/tags.mjs +238 -0
  128. package/src/rules/topology.mjs +333 -0
  129. package/src/tsconfig-paths.mjs +237 -0
  130. package/src/verdict.mjs +145 -0
  131. 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
+ }