@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,152 @@
1
+ /**
2
+ * The native project-model provider: `archkeep.json`, no Nx installed, no
3
+ * Nx-shaped `project.json` required.
4
+ *
5
+ * `../nx.mjs`'s `ProjectModelProvider` is a single call — `readProjectGraph`
6
+ * — because Nx already resolved projects, tags and edges before this package
7
+ * ever asked. Nothing here has done that resolving, so this provider's
8
+ * contract is two calls instead of one: `discover` answers "which projects,
9
+ * and which files does none of them own" from the tree alone, and `buildGraph`
10
+ * turns that plus the import sites analysis found into the same
11
+ * `{nodes, dependencies}` shape `../../rules/index.mjs`'s `evaluate` consumes.
12
+ * The split exists because import-site analysis needs projects to already be
13
+ * known (`../../workspace.mjs`'s `createWorkspace`/`analyzeWorkspace` resolve
14
+ * every relative import against a project list), so a single `readGraph` call
15
+ * would have to hide a whole analysis pass inside a provider — the layer this
16
+ * package's `../../../AGENTS.md` reserves for `../../workspace.mjs` alone.
17
+ *
18
+ * This module imports NOTHING from `../../workspace.mjs`: that file's import
19
+ * graph loads the TypeScript compiler at module scope
20
+ * (`../../process.mjs`'s header), and a workspace with no `.ts` file in it, or no
21
+ * `nx` installed, must still be able to discover its `archkeep.json` projects
22
+ * without paying for a compiler it will never call.
23
+ */
24
+ import { projectOwning } from "../../analysis/source-util.mjs";
25
+ import { ARCHKEEP_MODEL_FILE, loadNativeModel } from "./model.mjs";
26
+ import { discoverNativeProjects } from "./discover.mjs";
27
+ import { judgeCoverage } from "./coverage.mjs";
28
+ import { buildNativeGraph } from "./graph.mjs";
29
+
30
+ /**
31
+ * `projectOf`, over `../../analysis/source-util.mjs`'s `projectOwning` — the
32
+ * SAME longest-root-wins answer `../../workspace.mjs`'s `createWorkspace`
33
+ * gives the Nx path, so a file's owner does not depend on which provider
34
+ * found it.
35
+ *
36
+ * @param {{name: string, root: string}[]} projects
37
+ * @returns {(file: string) => string|undefined}
38
+ */
39
+ function projectOfFactory(projects) {
40
+ return (file) => projectOwning(projects, file)?.name ?? undefined;
41
+ }
42
+
43
+ /**
44
+ * Discovers a native workspace's projects and judges its coverage.
45
+ *
46
+ * Two failure classes, both loud (`../../../../../AGENTS.md`'s invariant): a model
47
+ * defect — thrown by `loadNativeModel` or `discoverNativeProjects` — and a
48
+ * stale coverage waiver, thrown here once discovery and coverage are both
49
+ * known. An unclaimed file, and an unparseable `project.json`
50
+ * (`./discover.mjs`'s `readProjectManifest`), are NOT throws: both become a
51
+ * `fileFailure` — `./coverage.mjs`'s and `./discover.mjs`'s own — that ride
52
+ * together in `DiscoveredWorkspace.failures`, because `../../../cli.mjs`'s
53
+ * existing exit-3 path already turns any non-empty `unchecked` count into
54
+ * that exit code once the failure reaches `../../report/text.mjs`'s
55
+ * `formatFailures` — the same mechanism a language analyzer's own
56
+ * `fileFailure` already uses, so nothing about either path is native-specific.
57
+ *
58
+ * @param {{root: string, files: string[], readFile: (path: string) => string|null}} args
59
+ * @returns {{projects: {name: string, root: string, type: string, tags: string[], tagOrigins: Record<string, string[]>, implicitDependencies: string[], targets: string[]}[], projectOf: (file: string) => string|undefined, model: object, failures: object[], exempted: string[]}}
60
+ * @throws {Error} on a malformed `archkeep.json`, a discovery defect, or a
61
+ * stale `coverage.exempt` row.
62
+ */
63
+ export function discover({ root, files, readFile }) {
64
+ const model = loadNativeModel(root, { readFile });
65
+ const { projects, failures: discoveryFailures } = discoverNativeProjects({
66
+ root,
67
+ files,
68
+ readFile,
69
+ model,
70
+ });
71
+ const projectOf = projectOfFactory(projects);
72
+ const coverage = judgeCoverage({ files, projectOf, exempt: model.coverage.exempt });
73
+
74
+ if (coverage.stale.length > 0) {
75
+ throw new Error(
76
+ `archkeep: ${root}/${ARCHKEEP_MODEL_FILE} describes a workspace that does not match the ` +
77
+ `tree:\n ` +
78
+ coverage.stale
79
+ .map(
80
+ (row) =>
81
+ `coverage.exempt: '${row.path}' matches no unclaimed file — either the files it ` +
82
+ `covered are now owned by a project, or the path was never right`,
83
+ )
84
+ .join("\n "),
85
+ );
86
+ }
87
+
88
+ // `coverage.exempted` used to stop here: `judgeCoverage` computed which
89
+ // files a `coverage.exempt` row removed from `unclaimed`, and nothing past
90
+ // this function ever read the list back — so a workspace could exempt an
91
+ // unbounded number of files from coverage, forever, with no command, no
92
+ // report line, and no JSON field ever naming a single one of them. An
93
+ // exempted file and a genuinely covered one were byte-for-byte
94
+ // indistinguishable in every surface this tool produces, which is exactly
95
+ // the silent direction `../../../../../AGENTS.md`'s invariant forbids —
96
+ // `check`'s own comment two lines above threatens a stale-exemption throw,
97
+ // but nothing threatened the opposite failure: an exemption nobody could see.
98
+ // `../../commands/context.mjs` threads this onto `CommandContext.analysis`
99
+ // and `../../../cli.mjs`'s `check` reports the count beside every verdict,
100
+ // the same "no violations is a claim about coverage too" treatment the
101
+ // import/file/project counts already get.
102
+ return {
103
+ projects,
104
+ projectOf,
105
+ model,
106
+ failures: [...discoveryFailures, ...coverage.failures],
107
+ exempted: coverage.exempted,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Builds the graph `evaluate()` judges, from a `discover()` result and the
113
+ * import sites analysis found across the discovered projects' files.
114
+ *
115
+ * `workspaceLayout` is threaded from `discovered.model` straight onto the
116
+ * graph object returned — declared-or-absent, never defaulted here, the same
117
+ * rule `./model.mjs`'s `normalizeNativeModel` documents (`../../rules/index.mjs`'s
118
+ * `createContext` is the one place that ever applies `DEFAULT_WORKSPACE_LAYOUT`).
119
+ *
120
+ * `exemptedFiles` threads the same way, from `discovered.exempted`: the
121
+ * concrete files `coverage.exempt` removed from coverage. This is the one
122
+ * seam both faces of a native workspace flow through — this CLI's native
123
+ * branch (`../../../src/commands/context.mjs`) and the language server's
124
+ * (`../../../src/lsp/workspace-index.mjs`) both build their graphs here — so
125
+ * threading it in the provider is what keeps an editor verdict and a
126
+ * `archkeep check` verdict on the same exempt-file import from disagreeing.
127
+ *
128
+ * @param {{discovered: ReturnType<typeof discover>, importSites: object[]}} args
129
+ * @returns {{nodes: Record<string, object>, dependencies: Record<string, object[]>, workspaceLayout?: {appsDir: string, libsDir: string}, exemptedFiles?: string[]}}
130
+ */
131
+ export function buildGraph({ discovered, importSites }) {
132
+ return buildNativeGraph({
133
+ projects: discovered.projects,
134
+ importSites,
135
+ projectOf: discovered.projectOf,
136
+ workspaceLayout: discovered.model.workspaceLayout,
137
+ exemptedFiles: discovered.exempted,
138
+ });
139
+ }
140
+
141
+ /**
142
+ * The `ProjectModelProvider`-family object `../../../cli.mjs` selects when a
143
+ * workspace root carries `archkeep.json` rather than `nx.json` (`../nx.mjs`'s
144
+ * `ProjectModelProvider` doc explains the seam both providers implement, and
145
+ * why this one's shape is two calls rather than `readProjectGraph`'s one).
146
+ */
147
+ export const nativeProvider = {
148
+ name: "native",
149
+ marker: ARCHKEEP_MODEL_FILE,
150
+ discover,
151
+ buildGraph,
152
+ };