@ecoma-io/archkeep 0.25.0 → 0.26.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/cli.mjs +166 -506
- package/commands.mjs +7 -1
- package/package.json +1 -1
- package/src/architecture-intent/judge.mjs +1 -1
- package/src/architecture-intent/model.mjs +34 -0
- package/src/canonical.mjs +2 -1
- package/src/commands/adr-for-workspace.mjs +63 -0
- package/src/commands/analyze-capability.mjs +13 -0
- package/src/commands/change-intent.mjs +11 -0
- package/src/commands/change.mjs +247 -34
- package/src/commands/check-capability.mjs +13 -0
- package/src/commands/check.mjs +13 -3
- package/src/commands/compare-capability.mjs +20 -0
- package/src/commands/completeness.mjs +12 -0
- package/src/commands/context-command.mjs +31 -0
- package/src/commands/coverage-verdict.mjs +15 -2
- package/src/commands/debt.mjs +30 -0
- package/src/commands/decisions.mjs +59 -10
- package/src/commands/delta.mjs +263 -49
- package/src/commands/diff.mjs +45 -0
- package/src/commands/discover.mjs +37 -0
- package/src/commands/drift.mjs +45 -0
- package/src/commands/evolution.mjs +22 -2
- package/src/commands/explain-capability.mjs +11 -0
- package/src/commands/explain.mjs +24 -0
- package/src/commands/fitness.mjs +119 -10
- package/src/commands/govern-capability.mjs +16 -0
- package/src/commands/graph.mjs +39 -0
- package/src/commands/health.mjs +34 -0
- package/src/commands/history.mjs +85 -2
- package/src/commands/impact.mjs +23 -0
- package/src/commands/inspect-capability.mjs +16 -0
- package/src/commands/plan-context-command.mjs +35 -0
- package/src/commands/policy.mjs +42 -4
- package/src/commands/provenance-command.mjs +15 -24
- package/src/commands/report.mjs +38 -0
- package/src/commands/rules-capability.mjs +16 -0
- package/src/commands/rules.mjs +3 -1
- package/src/commands/scenario.mjs +23 -0
- package/src/commands/trajectory.mjs +35 -0
- package/src/commands/waivers.mjs +30 -0
- package/src/corpus/goldens/.gitkeep +0 -0
- package/src/corpus/goldens/adr.json +62 -0
- package/src/corpus/goldens/adr.text +6 -0
- package/src/corpus/goldens/change.json +98 -0
- package/src/corpus/goldens/change.text +6 -0
- package/src/corpus/goldens/check.json +90 -0
- package/src/corpus/goldens/check.sarif +479 -0
- package/src/corpus/goldens/check.text +9 -0
- package/src/corpus/goldens/context.json +47 -0
- package/src/corpus/goldens/context.text +6 -0
- package/src/corpus/goldens/debt.json +59 -0
- package/src/corpus/goldens/debt.text +7 -0
- package/src/corpus/goldens/decisions.json +74 -0
- package/src/corpus/goldens/decisions.text +7 -0
- package/src/corpus/goldens/delta.json +94 -0
- package/src/corpus/goldens/delta.sarif +472 -0
- package/src/corpus/goldens/delta.text +5 -0
- package/src/corpus/goldens/diff.json +58 -0
- package/src/corpus/goldens/diff.text +4 -0
- package/src/corpus/goldens/discover.json +77 -0
- package/src/corpus/goldens/discover.text +10 -0
- package/src/corpus/goldens/drift.json +43 -0
- package/src/corpus/goldens/drift.text +3 -0
- package/src/corpus/goldens/evolution.json +328 -0
- package/src/corpus/goldens/evolution.text +32 -0
- package/src/corpus/goldens/explain.json +60 -0
- package/src/corpus/goldens/explain.text +7 -0
- package/src/corpus/goldens/fitness.json +45 -0
- package/src/corpus/goldens/fitness.text +3 -0
- package/src/corpus/goldens/graph.json +78 -0
- package/src/corpus/goldens/graph.text +10 -0
- package/src/corpus/goldens/health.json +72 -0
- package/src/corpus/goldens/health.text +10 -0
- package/src/corpus/goldens/help.text +104 -0
- package/src/corpus/goldens/history.json +43 -0
- package/src/corpus/goldens/history.text +4 -0
- package/src/corpus/goldens/impact.json +327 -0
- package/src/corpus/goldens/impact.text +10 -0
- package/src/corpus/goldens/provenance.json +453 -0
- package/src/corpus/goldens/provenance.text +14 -0
- package/src/corpus/goldens/reconcile.json +168 -0
- package/src/corpus/goldens/reconcile.text +3 -0
- package/src/corpus/goldens/report.json +168 -0
- package/src/corpus/goldens/report.text +42 -0
- package/src/corpus/goldens/rules verify.json +0 -0
- package/src/corpus/goldens/rules verify.text +0 -0
- package/src/corpus/goldens/scenario.json +441 -0
- package/src/corpus/goldens/scenario.text +28 -0
- package/src/corpus/goldens/trajectory.json +83 -0
- package/src/corpus/goldens/trajectory.text +10 -0
- package/src/corpus/goldens/usage-error.text +105 -0
- package/src/corpus/goldens/waivers.json +40 -0
- package/src/corpus/goldens/waivers.text +1 -0
- package/src/corpus/goldens-lsp/initialize.result.json +15 -0
- package/src/corpus/goldens-lsp/publish-cleared-empty.json +4 -0
- package/src/corpus/goldens-lsp/publish-fixed-empty.json +5 -0
- package/src/corpus/goldens-lsp/publish-unparseable.json +54 -0
- package/src/corpus/goldens-lsp/publish-violation.json +22 -0
- package/src/corpus/goldens-lsp/register-watched-files.json +72 -0
- package/src/go-work.mjs +8 -23
- package/src/governance/evolution-event.mjs +15 -0
- package/src/governance/evolution-store.mjs +59 -31
- package/src/governance/provenance-record.mjs +0 -152
- package/src/governance/verdict.mjs +26 -3
- package/src/intent/intent-manifest.json +14 -14
- package/src/lsp/server.mjs +5 -8
- package/src/lsp/workspace-index.mjs +55 -220
- package/src/options.mjs +10 -0
- package/src/providers/native/discover.mjs +13 -12
- package/src/providers/native/model.mjs +8 -3
- package/src/providers/nx-static.mjs +231 -0
- package/src/report/json.mjs +3 -2
- package/src/report/sarif.mjs +8 -3
- package/src/rules/edge-constraints.mjs +9 -0
- package/src/rules/index.mjs +26 -10
- package/src/rules/messages.mjs +64 -14
- package/src/rules/specifiers.mjs +21 -20
- package/src/tsconfig-paths.mjs +8 -14
- package/src/verdict.mjs +127 -16
|
@@ -6,33 +6,33 @@
|
|
|
6
6
|
* (`../rules/README.md`). Under Nx that graph arrives from Nx. A language
|
|
7
7
|
* server has no Nx — it is spawned by an editor, in a directory, with nothing
|
|
8
8
|
* else — so this module builds the same shape from whichever source of truth
|
|
9
|
-
* the root actually carries:
|
|
10
|
-
* `
|
|
11
|
-
* `buildGraph()` when
|
|
12
|
-
* `../providers/moon.mjs`'s
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `project.json
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* ## Why the native branch cannot reuse
|
|
9
|
+
* the root actually carries: `../providers/nx-static.mjs`'s static acquisition
|
|
10
|
+
* from the tracked `project.json` files when there is no `archkeep.json` at
|
|
11
|
+
* the root, `../providers/native/`'s own `discover()`/`buildGraph()` when
|
|
12
|
+
* there is (`buildNativeWorkspaceIndex` below), and `../providers/moon.mjs`'s
|
|
13
|
+
* one-call `readProjectGraph` when the root carries `.moon/` or
|
|
14
|
+
* `.config/moon/` (`buildMoonWorkspaceIndex`) — every acquisition through the
|
|
15
|
+
* provider layer (`../../AGENTS.md`, "`src/providers/` is the only layer
|
|
16
|
+
* allowed to build a graph"), never per-surface. Before the Moon branch
|
|
17
|
+
* existed, this module fell through to static discovery on a `.moon`-rooted
|
|
18
|
+
* tree — which finds a project only by its `project.json`, a file a Moon
|
|
19
|
+
* workspace never has — and built a zero-node index that read as clean while
|
|
20
|
+
* `check` exited 1 on the same tree.
|
|
21
|
+
*
|
|
22
|
+
* The static acquisition is the provider layer's, not this file's: what a
|
|
23
|
+
* `project.json` is, how its name resolves, which node type a project gets,
|
|
24
|
+
* how duplicate names are recorded — all of it lives in
|
|
25
|
+
* `../providers/nx-static.mjs`, beside the CLI-backed `readProjectGraph` it
|
|
26
|
+
* is the no-Nx counterpart of. This branch is the composition the seam
|
|
27
|
+
* contract assigns the caller: provider nodes in, annotations
|
|
28
|
+
* (`annotateMFERemotes`/`annotatePackageFacts`) and the workspace and the
|
|
29
|
+
* analysis and the edge folds composed around them, gap records out.
|
|
30
|
+
*
|
|
31
|
+
* ## Why the native branch cannot reuse the static acquisition
|
|
32
32
|
*
|
|
33
33
|
* A native workspace can declare or infer a project with no `project.json` at
|
|
34
|
-
* all — the whole point of `archkeep.json` is not needing one — and
|
|
35
|
-
*
|
|
34
|
+
* all — the whole point of `archkeep.json` is not needing one — and static
|
|
35
|
+
* discovery finds nothing there. A silently missing project is
|
|
36
36
|
* indistinguishable from a project that legitimately has no boundary
|
|
37
37
|
* violations, which is exactly the hole `../../../../AGENTS.md`'s invariant
|
|
38
38
|
* refuses ("An empty result is a claim, not a shrug"). So a root carrying
|
|
@@ -56,10 +56,11 @@
|
|
|
56
56
|
*
|
|
57
57
|
* No project name, no directory layout, no tag vocabulary (`../../AGENTS.md` —
|
|
58
58
|
* the tool is installed into workspaces it has never seen). Everything below is
|
|
59
|
-
* derived: projects from the `project.json` files that exist (
|
|
60
|
-
*
|
|
61
|
-
* node types from `projectType` by Nx's own rule either way, tags
|
|
62
|
-
* project's own list, edges from the imports the analyzers actually
|
|
59
|
+
* derived: projects from the `project.json` files that exist (static
|
|
60
|
+
* acquisition) or from `archkeep.json`'s declared∪inferred model (native
|
|
61
|
+
* branch), node types from `projectType` by Nx's own rule either way, tags
|
|
62
|
+
* from each project's own list, edges from the imports the analyzers actually
|
|
63
|
+
* find.
|
|
63
64
|
*
|
|
64
65
|
* ## Why the file list comes from git
|
|
65
66
|
*
|
|
@@ -113,12 +114,7 @@ import { join } from "node:path";
|
|
|
113
114
|
import { analyzeFile } from "../analysis/analyze.mjs";
|
|
114
115
|
import { fileFailure, isWholeFileFailure } from "../analysis/source-util.mjs";
|
|
115
116
|
import { containmentViolation } from "../containment.mjs";
|
|
116
|
-
import {
|
|
117
|
-
import {
|
|
118
|
-
NX_CONFIG_FILE,
|
|
119
|
-
readWorkspaceLayout,
|
|
120
|
-
requireCompleteWorkspaceLayout,
|
|
121
|
-
} from "../options.mjs";
|
|
117
|
+
import { NX_CONFIG_FILE, readWorkspaceLayout } from "../options.mjs";
|
|
122
118
|
import {
|
|
123
119
|
analyzeWorkspace,
|
|
124
120
|
annotateMFERemotes,
|
|
@@ -126,37 +122,17 @@ import {
|
|
|
126
122
|
createWorkspace,
|
|
127
123
|
listTrackedFiles,
|
|
128
124
|
} from "../workspace.mjs";
|
|
125
|
+
import { PROJECT_CONFIG_FILE } from "../providers/native/discover.mjs";
|
|
129
126
|
import { buildDependencies, mergeDeclaredEdges } from "../providers/native/graph.mjs";
|
|
130
127
|
import {
|
|
131
128
|
resolveDeclaredManifestEdges,
|
|
132
129
|
resolveDeclaredManifestFailures,
|
|
133
130
|
} from "../graph/create-dependencies.mjs";
|
|
134
|
-
import { nodeTypeOf, PROJECT_CONFIG_FILE } from "../providers/native/discover.mjs";
|
|
135
131
|
import { ARCHKEEP_MODEL_FILE } from "../providers/native/model.mjs";
|
|
136
132
|
import { nativeProvider } from "../providers/native/index.mjs";
|
|
137
133
|
import { requireSingleProjectModel } from "../providers/model-gate.mjs";
|
|
138
134
|
import { mergeImportEdges, moonProvider } from "../providers/moon.mjs";
|
|
139
|
-
|
|
140
|
-
export { PROJECT_CONFIG_FILE, nodeTypeOf, buildDependencies };
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* One `project.json` — or the `package.json` beside it — read the way Nx reads
|
|
144
|
-
* it, which is NOT `JSON.parse`.
|
|
145
|
-
*
|
|
146
|
-
* The reader itself is `../nx-json.mjs`, shared with `../options.mjs` because
|
|
147
|
-
* `nx.json` has to be read the same way for the same reason. This name stays as
|
|
148
|
-
* the local one because the stakes are specific to a project config, and worth
|
|
149
|
-
* stating where a reader of this module will look for them: losing a
|
|
150
|
-
* `project.json` here is the worst failure this server can have. The project
|
|
151
|
-
* leaves the graph; an import into it then resolves as external rather than
|
|
152
|
-
* cross-project; the rule engine's npm branch returns before the tag checks
|
|
153
|
-
* run; and the editor paints a real violation clean.
|
|
154
|
-
*
|
|
155
|
-
* @param {string} text
|
|
156
|
-
* @returns {object} Whatever the JSON describes.
|
|
157
|
-
* @throws {Error} when neither parser can read it.
|
|
158
|
-
*/
|
|
159
|
-
const parseProjectJson = parseNxJson;
|
|
135
|
+
import { readStaticProjectGraph } from "../providers/nx-static.mjs";
|
|
160
136
|
|
|
161
137
|
/**
|
|
162
138
|
* Every file git considers part of the working tree, workspace-relative and
|
|
@@ -188,127 +164,6 @@ export function listWorkspaceFiles(root) {
|
|
|
188
164
|
}
|
|
189
165
|
}
|
|
190
166
|
|
|
191
|
-
/** The directory part of a workspace-relative path; `""` at the tree root. */
|
|
192
|
-
const directoryOf = (file) => {
|
|
193
|
-
const slash = file.lastIndexOf("/");
|
|
194
|
-
return slash === -1 ? "" : file.slice(0, slash);
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* The projects declared in a tree, from its `project.json` files.
|
|
199
|
-
*
|
|
200
|
-
* A `project.json` that will not parse is SKIPPED and reported, not thrown on:
|
|
201
|
-
* one project being edited must not blank the graph for the other nineteen. The
|
|
202
|
-
* caller decides how loud to be about the ones that were skipped.
|
|
203
|
-
*
|
|
204
|
-
* @param {{files: string[], readFile: (path: string) => string|null}} tree
|
|
205
|
-
* @returns {{projects: {name: string, root: string, config: object}[], skipped: {file: string, reason: string}[]}}
|
|
206
|
-
*/
|
|
207
|
-
export function discoverProjects({ files, readFile }) {
|
|
208
|
-
// used by its own test
|
|
209
|
-
const projects = [];
|
|
210
|
-
const skipped = [];
|
|
211
|
-
for (const file of files) {
|
|
212
|
-
if (file !== PROJECT_CONFIG_FILE && !file.endsWith(`/${PROJECT_CONFIG_FILE}`)) continue;
|
|
213
|
-
const text = readFile(file);
|
|
214
|
-
if (text === null) {
|
|
215
|
-
skipped.push({ file, reason: "could not be read" });
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
let config;
|
|
219
|
-
try {
|
|
220
|
-
config = parseProjectJson(text);
|
|
221
|
-
} catch (cause) {
|
|
222
|
-
skipped.push({ file, reason: `is not valid JSON: ${cause?.message ?? cause}` });
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
const root = directoryOf(file);
|
|
226
|
-
// Nx's own precedence: the name a project states, then the one its
|
|
227
|
-
// `package.json` states, then the directory it lives in.
|
|
228
|
-
const packageName = (() => {
|
|
229
|
-
const manifest = readFile(root === "" ? "package.json" : `${root}/package.json`);
|
|
230
|
-
if (manifest === null) return undefined;
|
|
231
|
-
try {
|
|
232
|
-
// The same parser, because Nx reads this file with the same
|
|
233
|
-
// `readJsonFile` — a `package.json` Nx can name a project from must
|
|
234
|
-
// not become a project named after its directory here.
|
|
235
|
-
return parseProjectJson(manifest).name;
|
|
236
|
-
} catch {
|
|
237
|
-
return undefined;
|
|
238
|
-
}
|
|
239
|
-
})();
|
|
240
|
-
const name =
|
|
241
|
-
config.name ?? packageName ?? (root === "" ? "" : root.slice(root.lastIndexOf("/") + 1));
|
|
242
|
-
if (typeof name !== "string" || name === "") {
|
|
243
|
-
skipped.push({ file, reason: "declares no usable project name" });
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
projects.push({ name, root, config });
|
|
247
|
-
}
|
|
248
|
-
return { projects, skipped };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* The graph nodes for a project list, in Nx's shape: `data` is the project's
|
|
253
|
-
* own configuration with `tags` guaranteed present, because `../rules/tags.mjs`
|
|
254
|
-
* reads it unguarded and an absent list is not the same fact as an empty one.
|
|
255
|
-
*
|
|
256
|
-
* @param {{name: string, root: string, config: object}[]} projects
|
|
257
|
-
* @returns {{nodes: Record<string, object>, duplicateProjects: {name: string, roots: string[]}[]}}
|
|
258
|
-
* `duplicateProjects` names every name two or more projects resolved to and
|
|
259
|
-
* every root that claimed it (#375): a silent `nodes[name] = …` overwrite
|
|
260
|
-
* drops the shadowed project from the graph, its files match no root, and
|
|
261
|
-
* the editor publishes no diagnostics for real boundary crossings — the
|
|
262
|
-
* exact silent direction `../../../../AGENTS.md`'s invariant refuses. The
|
|
263
|
-
* first project still wins in `nodes` (the index stays usable); the caller
|
|
264
|
-
* publishes the collision through `indexGaps`.
|
|
265
|
-
*/
|
|
266
|
-
export function buildNodes(projects) {
|
|
267
|
-
// used by its own test
|
|
268
|
-
// Null-prototype for the same reason `../providers/native/graph.mjs` and
|
|
269
|
-
// `../providers/moon.mjs` use them: every key here is a project NAME, and
|
|
270
|
-
// project names come from a `project.json`'s own `name` field —
|
|
271
|
-
// attacker-supplied the moment a pull request adds a project called
|
|
272
|
-
// `__proto__`. A plain `{}` answers `nodes["__proto__"] = …` by repointing
|
|
273
|
-
// the object's OWN prototype rather than adding an entry, so the project
|
|
274
|
-
// vanishes from `graph.nodes` while `filesOf` still attributes it files — a
|
|
275
|
-
// real cross-project import into it then read a poisoned Node as a graph
|
|
276
|
-
// node and flips/throws on every rule that touches it. `Object.create(null)`
|
|
277
|
-
// has no inherited `__proto__` accessor to collide with, so the name behaves
|
|
278
|
-
// like every other project name: a real, own, enumerable entry.
|
|
279
|
-
const nodes = Object.create(null);
|
|
280
|
-
/** @type {Map<string, string>} name → root of the first project that claimed it. */
|
|
281
|
-
const seenNames = new Map();
|
|
282
|
-
/** @type {Map<string, string[]>} name → every root that resolved to it, for names claimed twice or more. */
|
|
283
|
-
const duplicateMap = new Map();
|
|
284
|
-
|
|
285
|
-
for (const { name, root, config } of projects) {
|
|
286
|
-
if (seenNames.has(name)) {
|
|
287
|
-
// Duplicate name detected — record it for loud reporting
|
|
288
|
-
if (!duplicateMap.has(name)) {
|
|
289
|
-
duplicateMap.set(name, [seenNames.get(name)]);
|
|
290
|
-
}
|
|
291
|
-
duplicateMap.get(name).push(root);
|
|
292
|
-
// Skip adding the duplicate to nodes — first project wins
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
seenNames.set(name, root);
|
|
296
|
-
nodes[name] = {
|
|
297
|
-
name,
|
|
298
|
-
type: nodeTypeOf(name, config.projectType),
|
|
299
|
-
data: { ...config, root, tags: config.tags ?? [] },
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Convert the duplicate map to the expected output format
|
|
304
|
-
const duplicateProjects = [];
|
|
305
|
-
for (const [name, roots] of duplicateMap.entries()) {
|
|
306
|
-
duplicateProjects.push({ name, roots });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return { nodes, duplicateProjects };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
167
|
/**
|
|
313
168
|
* Everything a diagnosis needs about the tree, computed once.
|
|
314
169
|
*
|
|
@@ -372,7 +227,7 @@ export function buildWorkspaceIndex({
|
|
|
372
227
|
const { hasNx, moonMarker } = requireSingleProjectModel(root, { exists: pathExists });
|
|
373
228
|
// A root carrying ARCHKEEP_MODEL_FILE has a project model this module does
|
|
374
229
|
// not read from `project.json` at all — see this file's header — so it is
|
|
375
|
-
// handed to the native branch below rather than to
|
|
230
|
+
// handed to the native branch below rather than to the static acquisition.
|
|
376
231
|
//
|
|
377
232
|
// Detected by READING the file, not by whether git tracks it: `../../cli.mjs`
|
|
378
233
|
// and this server's own `readWorkspaceOptions` (`./server.mjs`'s `markersAt`)
|
|
@@ -381,7 +236,7 @@ export function buildWorkspaceIndex({
|
|
|
381
236
|
// to the tree but not yet `git add`ed) exists by that test. Dispatching here
|
|
382
237
|
// on `files.includes(...)` instead — `files` is the TRACKED list `listFiles`
|
|
383
238
|
// returns — disagreed with both of them: this branch would fall through to
|
|
384
|
-
//
|
|
239
|
+
// the static acquisition, find no `project.json` for a native-only tree, and
|
|
385
240
|
// build a zero-node, zero-edge index that publishes `analyzed: true` with an
|
|
386
241
|
// empty diagnostic list on a workspace `archkeep check` exits 1 on — the gap
|
|
387
242
|
// machinery below has no entry for "wrong provider" to report. `readFile`
|
|
@@ -395,8 +250,14 @@ export function buildWorkspaceIndex({
|
|
|
395
250
|
return buildMoonWorkspaceIndex({ root, files, readFile, tsConfig, readGraph });
|
|
396
251
|
}
|
|
397
252
|
|
|
398
|
-
|
|
399
|
-
|
|
253
|
+
// Static acquisition — see this file's header. The provider returns the
|
|
254
|
+
// nodes, the projects it skipped on the way, the duplicate-name records, and
|
|
255
|
+
// `nx.json`'s `workspaceLayout` merged onto the graph (`./nx.mjs`
|
|
256
|
+
// `readProjectGraph`'s own merge, in its caught-into-a-record form — that
|
|
257
|
+
// function's doc owns why the merge exists at all); this branch composes
|
|
258
|
+
// everything else around them.
|
|
259
|
+
const { nodes, skippedProjects, duplicateProjects, workspaceLayout, workspaceLayoutFailure } =
|
|
260
|
+
readStaticProjectGraph({ root, files, readFile, readLayout });
|
|
400
261
|
// The same Module Federation fact the CLI path computes, from the same
|
|
401
262
|
// predicate (`../workspace.mjs` → `annotateMFERemotes`): a CLI verdict and an
|
|
402
263
|
// editor verdict on the same import must match, and the field failing closed
|
|
@@ -408,8 +269,8 @@ export function buildWorkspaceIndex({
|
|
|
408
269
|
// write would fail closed — extra reports, not waived ones — but the two
|
|
409
270
|
// faces would then disagree about the same import, which is the line
|
|
410
271
|
// `src/lsp/` exists to hold. It also DELETES a stale `entryPoints` or
|
|
411
|
-
// `declaredPackages` riding in from `project.json` (`
|
|
412
|
-
// config into `data` verbatim), because an unmeasured claim that waives
|
|
272
|
+
// `declaredPackages` riding in from `project.json` (`../providers/nx-static.mjs`'s
|
|
273
|
+
// `buildNodes` spreads that config into `data` verbatim), because an unmeasured claim that waives
|
|
413
274
|
// violations is the silent direction.
|
|
414
275
|
annotatePackageFacts(nodes, readFile);
|
|
415
276
|
|
|
@@ -443,34 +304,9 @@ export function buildWorkspaceIndex({
|
|
|
443
304
|
// silently missing edges.
|
|
444
305
|
const manifestRefusalFailures = resolveDeclaredManifestFailures(workspace);
|
|
445
306
|
|
|
446
|
-
// `nx.json`'s `workspaceLayout` reaches the rule engine here the same way
|
|
447
|
-
// `../providers/nx.mjs`'s `readProjectGraph` merges it onto the graph it
|
|
448
|
-
// returns to `cli.mjs` — see that function's own doc for why a merge step
|
|
449
|
-
// exists at all (`nx graph --file=` itself emits no such key) and why a
|
|
450
|
-
// declared-but-incomplete layout is refused rather than completed
|
|
451
|
-
// (`requireCompleteWorkspaceLayout`, `../options.mjs`). Without this, an
|
|
452
|
-
// editor open on a workspace with a non-default `appsDir`/`libsDir` would
|
|
453
|
-
// draw no diagnostic for exactly the import `archkeep check` flags on the
|
|
454
|
-
// same tree — the language server's own stated rule (this package's
|
|
455
|
-
// `AGENTS.md`, "An empty diagnostic list must mean 'no violation'"),
|
|
456
|
-
// violated from the direction it exists to catch. A read/validation
|
|
457
|
-
// failure is caught rather than thrown onward — one malformed `nx.json`
|
|
458
|
-
// must not blank the whole index — and recorded as `workspaceLayoutFailure`
|
|
459
|
-
// for `indexGaps` to turn into a diagnostic, the same shape
|
|
460
|
-
// `nativeModelFailure` already uses for the native branch's own
|
|
461
|
-
// model-load failure.
|
|
462
|
-
let workspaceLayout;
|
|
463
|
-
let workspaceLayoutFailure = null;
|
|
464
|
-
try {
|
|
465
|
-
const declared = requireCompleteWorkspaceLayout(readLayout(root));
|
|
466
|
-
if (declared !== null) workspaceLayout = declared;
|
|
467
|
-
} catch (cause) {
|
|
468
|
-
workspaceLayoutFailure = cause?.message ?? String(cause);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
307
|
// An Nx-marked root that yielded no project at all is a tree this branch
|
|
472
308
|
// could not see the shape of, not a tree with nothing in it.
|
|
473
|
-
//
|
|
309
|
+
// The static acquisition finds a project only by its `project.json`, and a
|
|
474
310
|
// PACKAGE-BASED Nx workspace has none: its projects are declared in
|
|
475
311
|
// `package.json` files, which this module reads only to resolve the NAME of a
|
|
476
312
|
// project a `project.json` already found. `../providers/nx.mjs`'s
|
|
@@ -517,7 +353,7 @@ export function buildWorkspaceIndex({
|
|
|
517
353
|
files,
|
|
518
354
|
workspace,
|
|
519
355
|
graph,
|
|
520
|
-
skippedProjects
|
|
356
|
+
skippedProjects,
|
|
521
357
|
fileFailures: [...fileFailures, ...manifestRefusalFailures],
|
|
522
358
|
// Retained past the graph build — the evidence half of `evaluate()`'s
|
|
523
359
|
// input (`./diagnose.mjs` composes its run from these). See this module
|
|
@@ -589,8 +425,8 @@ function analyzeTrackedFiles({ files, workspace }) {
|
|
|
589
425
|
|
|
590
426
|
/**
|
|
591
427
|
* The native branch of `buildWorkspaceIndex`: drives `../providers/native/`'s
|
|
592
|
-
* two-call contract (`discover()` then `buildGraph()`) instead of
|
|
593
|
-
* `
|
|
428
|
+
* two-call contract (`discover()` then `buildGraph()`) instead of the static
|
|
429
|
+
* acquisition (`../providers/nx-static.mjs`), because a `archkeep.json` project can have no
|
|
594
430
|
* `project.json` at all — see this module's header.
|
|
595
431
|
*
|
|
596
432
|
* @param {{root: string, files: string[], readFile: (path: string) => string|null, tsConfig?: string}} args
|
|
@@ -711,10 +547,9 @@ function buildNativeWorkspaceIndex({ root, files, readFile, tsConfig }) {
|
|
|
711
547
|
/**
|
|
712
548
|
* The Moon branch of `buildWorkspaceIndex`: drives `../providers/moon.mjs`'s
|
|
713
549
|
* one-call contract (`readProjectGraph`) — the same provider object
|
|
714
|
-
* `../commands/context.mjs` hands `check` on this tree — instead of
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
* replaces.
|
|
550
|
+
* `../commands/context.mjs` hands `check` on this tree — instead of the static
|
|
551
|
+
* acquisition, because a Moon project has no `project.json` for it to find. See
|
|
552
|
+
* this module's header for the fall-through this branch replaces.
|
|
718
553
|
*
|
|
719
554
|
* @param {{root: string, files: string[], readFile: (path: string) => string|null, tsConfig?: string, readGraph: (root: string) => object}} args
|
|
720
555
|
* @returns {ReturnType<typeof buildWorkspaceIndex>}
|
|
@@ -859,7 +694,7 @@ function buildMoonWorkspaceIndex({ root, files, readFile, tsConfig, readGraph })
|
|
|
859
694
|
* command whose failure a developer has to resolve.
|
|
860
695
|
* - **An `nxModelFailure` gap is that same family again, for the Nx-shaped
|
|
861
696
|
* branch's own project discovery.** Present only while `nx.json` marks the
|
|
862
|
-
* root AND
|
|
697
|
+
* root AND the static acquisition found no project in it at all — never merely
|
|
863
698
|
* because the root carries `nx.json`. A package-based Nx workspace (projects
|
|
864
699
|
* declared in `package.json`, no `project.json` anywhere) is the shape that
|
|
865
700
|
* reaches it, and reading that shape is NOT what this server does about it:
|
|
@@ -873,8 +708,8 @@ function buildMoonWorkspaceIndex({ root, files, readFile, tsConfig, readGraph })
|
|
|
873
708
|
* equivalent of `nativeModelFailure`, not a second copy of it.** It is
|
|
874
709
|
* present only while `NX_CONFIG_FILE`'s own `workspaceLayout` is malformed
|
|
875
710
|
* or declared partially (`../options.mjs`'s `readWorkspaceLayout` /
|
|
876
|
-
* `requireCompleteWorkspaceLayout`, called from
|
|
877
|
-
*
|
|
711
|
+
* `requireCompleteWorkspaceLayout`, called from the static acquisition
|
|
712
|
+
* `../providers/nx-static.mjs`) — never on the native branch, where the identically-shaped
|
|
878
713
|
* failure already surfaces as `nativeModelFailure` instead (see
|
|
879
714
|
* `buildNativeWorkspaceIndex`). It **clears itself** the same way: `nx.json`
|
|
880
715
|
* is already a watched file (`./server.mjs`), so fixing it republishes
|
package/src/options.mjs
CHANGED
|
@@ -120,6 +120,16 @@ export const DEFAULT_OPTIONS = Object.freeze({
|
|
|
120
120
|
/** The file Nx reads to learn a workspace exists — and where the options live. */
|
|
121
121
|
export const NX_CONFIG_FILE = "nx.json";
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* The native workspace's root marker and model file — `nx.json`'s
|
|
125
|
+
* counterpart for a tree with no Nx at all. The options layer owns every
|
|
126
|
+
* filename a workspace's declaration can be read from, so the name is
|
|
127
|
+
* defined here rather than beside the loader that parses it
|
|
128
|
+
* (`./providers/native/model.mjs`, which re-exports this binding): help
|
|
129
|
+
* text and `--output` guards need the name without importing the loader.
|
|
130
|
+
*/
|
|
131
|
+
export const ARCHKEEP_MODEL_FILE = "archkeep.json";
|
|
132
|
+
|
|
123
133
|
/**
|
|
124
134
|
* Merges a raw options object over the defaults, refusing anything it does not
|
|
125
135
|
* recognise.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* manifest matching `projects.infer` (`project.json`, `package.json`, `go.mod`,
|
|
9
9
|
* `Cargo.toml`, `pyproject.toml` unless the model says otherwise) contributes
|
|
10
10
|
* one more UNLESS its directory is already a declared root. Name precedence
|
|
11
|
-
* reproduces Nx's own exactly —
|
|
11
|
+
* reproduces Nx's own exactly — `../nx-static.mjs`'s `discoverProjects`
|
|
12
12
|
* is the oracle: `config.name ?? packageName ?? directoryBasename` — so a tree
|
|
13
13
|
* that used to run Nx and now runs `archkeep.json` names its projects the same
|
|
14
14
|
* way it always did.
|
|
@@ -21,15 +21,16 @@
|
|
|
21
21
|
* (`../../rules/tags.mjs`) only ever needs to know a project HAS a tag, never
|
|
22
22
|
* where it came from.
|
|
23
23
|
*
|
|
24
|
-
* `nodeTypeOf` and `PROJECT_CONFIG_FILE` live here rather than
|
|
24
|
+
* `nodeTypeOf` and `PROJECT_CONFIG_FILE` live here rather than beside their
|
|
25
|
+
* consumers — `../nx-static.mjs` (the static Nx acquisition) and
|
|
25
26
|
* `../../lsp/workspace-index.mjs`, which used to define them: this module is
|
|
26
27
|
* the promotion target (`../../../AGENTS.md`, "`src/providers/` is the layer
|
|
27
|
-
* that supplies a graph to `evaluate()`"), and
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `../../workspace.mjs
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* that supplies a graph to `evaluate()`"), and both consumers import them
|
|
29
|
+
* from here. Defining them in the acquisition instead would point this
|
|
30
|
+
* module at its own consumer for two functions that need nothing beyond a
|
|
31
|
+
* string; defining them in `../../lsp/workspace-index.mjs` would be worse —
|
|
32
|
+
* that module imports `../../workspace.mjs`, which loads the TypeScript
|
|
33
|
+
* compiler at module scope (`../../process.mjs`'s header).
|
|
33
34
|
*/
|
|
34
35
|
import { basenameMatches } from "../../analysis/manifest-util.mjs";
|
|
35
36
|
import { fileFailure } from "../../analysis/source-util.mjs";
|
|
@@ -80,7 +81,7 @@ const basenameOf = (root) => (root === "" ? "" : root.slice(root.lastIndexOf("/"
|
|
|
80
81
|
/**
|
|
81
82
|
* `project.json` at `projectRoot`, parsed the same JSONC-tolerant way
|
|
82
83
|
* `../../nx-json.mjs` reads every other config this package trusts —
|
|
83
|
-
*
|
|
84
|
+
* `../nx-static.mjs` reads its copy of `project.json` the
|
|
84
85
|
* same way, so a trailing comma or a comment that Nx itself accepts is not a
|
|
85
86
|
* reason for this provider to disagree with it.
|
|
86
87
|
*
|
|
@@ -146,7 +147,7 @@ function readProjectManifest(projectRoot, readFile, isTracked) {
|
|
|
146
147
|
* `nx`, `dist/plugins/package-json.js`'s `createNodeFromPackageJson` reads
|
|
147
148
|
* every tracked `package.json` that way, the same `jsonc-parser` Nx reads
|
|
148
149
|
* `nx.json` and `project.json` with — even though `npm install` itself is
|
|
149
|
-
* strict JSON about the same file.
|
|
150
|
+
* strict JSON about the same file. `../nx-static.mjs`'s
|
|
150
151
|
* `discoverProjects`, the oracle this module's own header cites for name
|
|
151
152
|
* precedence, reads `package.json` through that same reader
|
|
152
153
|
* (`parseProjectJson`, its local name for `parseNxJson`) for exactly that
|
|
@@ -355,7 +356,7 @@ export function discoverNativeProjects({ root, files, readFile, model }) {
|
|
|
355
356
|
// gating this read on `manifest` being falsy skipped `package.json`
|
|
356
357
|
// entirely for that project, landing straight on the directory basename
|
|
357
358
|
// and skipping the middle rung of the precedence chain below.
|
|
358
|
-
//
|
|
359
|
+
// `../nx-static.mjs`'s `discoverProjects` — the oracle this
|
|
359
360
|
// module's own header names for this exact precedence — reads
|
|
360
361
|
// `package.json` the same unconditional way for the same reason.
|
|
361
362
|
const { name: packageName, failure: packageFailure } = readPackageName(
|
|
@@ -364,7 +365,7 @@ export function discoverNativeProjects({ root, files, readFile, model }) {
|
|
|
364
365
|
isTracked,
|
|
365
366
|
);
|
|
366
367
|
if (packageFailure) failures.push(packageFailure);
|
|
367
|
-
// Nx's own precedence, reproduced exactly (
|
|
368
|
+
// Nx's own precedence, reproduced exactly (`../nx-static.mjs`,
|
|
368
369
|
// `discoverProjects`): a declared name, then `project.json`'s, then
|
|
369
370
|
// `package.json`'s, then the directory basename.
|
|
370
371
|
const name = declared?.name ?? manifest?.name ?? packageName ?? basenameOf(projectRoot);
|
|
@@ -44,12 +44,17 @@
|
|
|
44
44
|
* shows the first is the slower way to get to a working config.
|
|
45
45
|
*/
|
|
46
46
|
import { globComplexityError, projectPatternError, safeMatchesGlob } from "../../rules/match.mjs";
|
|
47
|
-
import { resolveOptions } from "../../options.mjs";
|
|
47
|
+
import { ARCHKEEP_MODEL_FILE, resolveOptions } from "../../options.mjs";
|
|
48
48
|
import { findBoundaryConfigViolations, policyKeyViolations } from "../../config.mjs";
|
|
49
49
|
import { describe, isPlainObject, isStringArray } from "../../values.mjs";
|
|
50
50
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The file this provider treats as a workspace root marker and its model —
|
|
53
|
+
* defined once in `../../options.mjs`, the filename-knowing layer, and
|
|
54
|
+
* re-exported here under the same binding this module and every provider
|
|
55
|
+
* consumer already import.
|
|
56
|
+
*/
|
|
57
|
+
export { ARCHKEEP_MODEL_FILE };
|
|
53
58
|
|
|
54
59
|
/**
|
|
55
60
|
* Copies every character of `text` to `out` up to and including the closing
|