@ecoma-io/archkeep 0.22.2 → 0.24.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/package.json +40 -13
- package/src/analysis/jvm/packages.mjs +0 -17
- package/src/analysis/manifest-util.mjs +14 -5
- package/src/analysis/markdown.mjs +340 -0
- package/src/analysis/source-util.mjs +5 -4
- package/src/analysis/typescript.mjs +146 -0
- package/src/architecture-intent/judge.mjs +1 -1
- package/src/architecture-intent/model.mjs +3 -12
- package/src/commands/README.md +16 -7
- package/src/commands/change-intent.mjs +2 -11
- package/src/commands/check.mjs +234 -12
- package/src/commands/completeness.mjs +0 -32
- package/src/commands/context-command.mjs +13 -21
- package/src/commands/context.mjs +46 -47
- package/src/commands/coverage-verdict.mjs +12 -2
- package/src/commands/delta-snapshot.mjs +1 -5
- package/src/commands/diff.mjs +1 -1
- package/src/commands/discover.mjs +7 -3
- package/src/commands/evaluation-primitives.mjs +6 -2
- package/src/commands/explain.mjs +17 -20
- package/src/commands/graph.mjs +7 -0
- package/src/commands/health.mjs +4 -0
- package/src/commands/impact-reachability.mjs +104 -0
- package/src/commands/impact.mjs +9 -71
- package/src/commands/plan-context-command.mjs +5 -1
- package/src/commands/policy.mjs +4 -4
- package/src/commands/provenance.mjs +8 -2
- package/src/commands/scenario-evaluation.mjs +1 -1
- package/src/config.mjs +171 -17
- package/src/custom-rules/evidence.mjs +1 -1
- package/src/custom-rules/host.mjs +2 -2
- package/src/custom-rules/values.mjs +8 -3
- package/src/errors.mjs +24 -2
- package/src/eslint-config.mjs +2 -5
- package/src/fixtures/evolution-lifecycle/workspace.mjs +0 -5
- package/src/governance/adr-registry.mjs +33 -17
- package/src/governance/decision-graph.mjs +1 -1
- package/src/governance/evolution-store.mjs +36 -18
- package/src/governance/fitness-registry.mjs +1 -14
- package/src/governance/profile-registry.mjs +20 -23
- package/src/governance/provenance-record.mjs +1 -11
- package/src/governance/reconcile-score.mjs +0 -3
- package/src/governance/row-schema.mjs +1 -14
- package/src/governance/verdict.mjs +168 -4
- package/src/intent/intent-manifest.json +16 -16
- package/src/lsp/diagnose.mjs +2 -2
- package/src/lsp/server.mjs +1 -1
- package/src/lsp/workspace-index.mjs +3 -3
- package/src/options.mjs +1 -1
- package/src/providers/model-gate.mjs +59 -0
- package/src/providers/moon.mjs +6 -6
- package/src/providers/native/model.mjs +2 -16
- package/src/report/README.md +13 -7
- package/src/report/evidence.mjs +11 -168
- package/src/report/json.mjs +10 -7
- package/src/report/sarif.mjs +29 -4
- package/src/report/text.mjs +39 -0
- package/src/rules/README.md +18 -9
- package/src/{commands → rules}/edge-constraints.mjs +18 -12
- package/src/rules/index.mjs +30 -0
- package/src/values.mjs +49 -0
- package/src/verdict.mjs +58 -7
- package/src/workspace.mjs +29 -0
package/src/verdict.mjs
CHANGED
|
@@ -8,9 +8,15 @@
|
|
|
8
8
|
* `../cli.mjs`'s `runCheck` takes the process's exit code from the same call.
|
|
9
9
|
* `../cli.mjs` re-exports `EXIT` under its own name, so every importer that
|
|
10
10
|
* already reads it from there keeps working.
|
|
11
|
+
*
|
|
12
|
+
* `EXIT` is the one place a status→exit-code number is written. The
|
|
13
|
+
* status-keyed view of it, `EXIT_FOR_STATUS`, is derived from `EXIT` and is
|
|
14
|
+
* what the envelope's consistency check (`./report/json.mjs`) asserts against,
|
|
15
|
+
* so a consumer-facing status and the process's own exit code can never drift
|
|
16
|
+
* into two encodings of one contract.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
|
-
import { buildDecision } from "./
|
|
19
|
+
import { buildDecision } from "./governance/verdict.mjs";
|
|
14
20
|
|
|
15
21
|
export const EXIT = Object.freeze({
|
|
16
22
|
ok: 0,
|
|
@@ -18,6 +24,20 @@ export const EXIT = Object.freeze({
|
|
|
18
24
|
usage: 2,
|
|
19
25
|
error: 3,
|
|
20
26
|
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The envelope `status`→`exitCode` view of `EXIT` — the one mapping
|
|
30
|
+
* `jsonEnvelope` asserts every command's envelope against. Derived from
|
|
31
|
+
* `EXIT` rather than restated, so the numbers are written exactly once;
|
|
32
|
+
* `usage` has no status because a usage error never reaches an envelope.
|
|
33
|
+
*
|
|
34
|
+
* @type {Readonly<Record<"ok"|"findings"|"no-verdict", 0|1|3>>}
|
|
35
|
+
*/
|
|
36
|
+
export const EXIT_FOR_STATUS = Object.freeze({
|
|
37
|
+
ok: EXIT.ok,
|
|
38
|
+
findings: EXIT.violations,
|
|
39
|
+
"no-verdict": EXIT.error,
|
|
40
|
+
});
|
|
21
41
|
/**
|
|
22
42
|
* The coverage clauses of a no-verdict reason, spelled once — the strings
|
|
23
43
|
* `verdictFor` joins into `decision.reason` and `check`'s text report renders
|
|
@@ -41,6 +61,33 @@ export function coverageIncompleteReasons({ unchecked, blindSpots, analyzed }) {
|
|
|
41
61
|
analyzed === 0 ? "no file in scope could be analyzed — coverage incomplete" : null,
|
|
42
62
|
].filter(Boolean);
|
|
43
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The one completeness predicate — the three coverage axes conjoined, the
|
|
67
|
+
* boolean twin of `coverageIncompleteReasons` directly above, which words the
|
|
68
|
+
* same axes as clauses. `coverageVerdict` (`./commands/coverage-verdict.mjs`)
|
|
69
|
+
* reads its `complete` from here, `verdictFor` reads the decision's
|
|
70
|
+
* `coverageComplete` from here, and `check`'s coverage block reads its
|
|
71
|
+
* `complete` from here — three faces of one claim, so the envelope's
|
|
72
|
+
* `coverage.complete` and its `decision.coverageComplete` cannot disagree
|
|
73
|
+
* about a run neither re-derives from the other.
|
|
74
|
+
*
|
|
75
|
+
* The counts come from the caller because a command's coverage universe is
|
|
76
|
+
* its own: `check`'s is wider than `commandContext.analysis` (the go.work and
|
|
77
|
+
* tsconfig whole-file failures it pushes, the accepted `coverage.unowned`
|
|
78
|
+
* files it withdraws), and each face feeds the counts it is a claim about. A
|
|
79
|
+
* caller that reads plain `commandContext.analysis` should call
|
|
80
|
+
* `coverageVerdict` instead — this predicate is the law's last step, not the
|
|
81
|
+
* place failure classes get decided (`./analysis/source-util.mjs`'s
|
|
82
|
+
* classifiers own that line).
|
|
83
|
+
*
|
|
84
|
+
* @param {{unchecked: number, blindSpotCount: number, analyzed: number}} counts
|
|
85
|
+
* @returns {boolean} Whether the run judged everything in its scope.
|
|
86
|
+
*/
|
|
87
|
+
export function coverageComplete({ unchecked, blindSpotCount, analyzed }) {
|
|
88
|
+
return unchecked === 0 && blindSpotCount === 0 && analyzed > 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
44
91
|
/**
|
|
45
92
|
* The one place that turns a run's counts into the verdict every format
|
|
46
93
|
* agrees on. `runCheck` uses it for the process's exit code; `check` uses the
|
|
@@ -57,7 +104,7 @@ export function coverageIncompleteReasons({ unchecked, blindSpots, analyzed }) {
|
|
|
57
104
|
* "checked, and fine".
|
|
58
105
|
*
|
|
59
106
|
* The `decision` is the canonical 4-state verb of the same verdict
|
|
60
|
-
* (`./
|
|
107
|
+
* (`./governance/verdict.mjs`), built from the same counts so the envelope's
|
|
61
108
|
* `status` and its `decision.verdict` cannot disagree: `ok`→`pass`,
|
|
62
109
|
* `findings`→`fail`, `no-verdict`→`unknown`. `buildDecision` throws on any
|
|
63
110
|
* invariant the counts violate (a `pass` over incomplete coverage, a `fail`
|
|
@@ -103,11 +150,15 @@ export function verdictFor({
|
|
|
103
150
|
) {
|
|
104
151
|
return {
|
|
105
152
|
status: "findings",
|
|
106
|
-
exitCode:
|
|
153
|
+
exitCode: EXIT_FOR_STATUS.findings,
|
|
107
154
|
reasons: coverageReasons,
|
|
108
155
|
decision: buildDecision({
|
|
109
156
|
status: "findings",
|
|
110
|
-
|
|
157
|
+
// The one completeness predicate, not a restatement: the decision's
|
|
158
|
+
// `coverageComplete` and the envelope's `coverage.complete` are the
|
|
159
|
+
// same claim about the same counts (`check` feeds both from one
|
|
160
|
+
// object), so they read it from one expression.
|
|
161
|
+
coverageComplete: coverageComplete({ unchecked, blindSpotCount: blindSpots, analyzed }),
|
|
111
162
|
findings:
|
|
112
163
|
violations +
|
|
113
164
|
declaredEdgeFindings +
|
|
@@ -164,11 +215,11 @@ export function verdictFor({
|
|
|
164
215
|
].filter(Boolean);
|
|
165
216
|
return {
|
|
166
217
|
status: "no-verdict",
|
|
167
|
-
exitCode:
|
|
218
|
+
exitCode: EXIT_FOR_STATUS["no-verdict"],
|
|
168
219
|
reasons,
|
|
169
220
|
decision: buildDecision({
|
|
170
221
|
status: "no-verdict",
|
|
171
|
-
coverageComplete: unchecked
|
|
222
|
+
coverageComplete: coverageComplete({ unchecked, blindSpotCount: blindSpots, analyzed }),
|
|
172
223
|
findings: 0,
|
|
173
224
|
reason: reasons.join("; "),
|
|
174
225
|
}),
|
|
@@ -176,7 +227,7 @@ export function verdictFor({
|
|
|
176
227
|
}
|
|
177
228
|
return {
|
|
178
229
|
status: "ok",
|
|
179
|
-
exitCode:
|
|
230
|
+
exitCode: EXIT_FOR_STATUS.ok,
|
|
180
231
|
reasons: [],
|
|
181
232
|
decision: buildDecision({
|
|
182
233
|
status: "ok",
|
package/src/workspace.mjs
CHANGED
|
@@ -177,6 +177,35 @@ export function listTrackedFiles(workspaceRoot, { run = runProcess } = {}) {
|
|
|
177
177
|
return out.split("\0").filter((path) => path !== "");
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Every file present in the worktree that git does NOT track, workspace-
|
|
182
|
+
* relative — the complement of `listTrackedFiles` above, and the answer to
|
|
183
|
+
* the only question that pair can ask: what exists in the tree the tracked
|
|
184
|
+
* universe was cut from, but never entered it (#675).
|
|
185
|
+
*
|
|
186
|
+
* `--exclude-standard` is what keeps this answer a git answer and not a walk
|
|
187
|
+
* of our own: ignored files — build outputs, dependency installs, anything a
|
|
188
|
+
* `.gitignore`, `.git/info/exclude` or `core.excludesFile` names — are not
|
|
189
|
+
* part of the workspace's population, exactly as for the tracked list. The
|
|
190
|
+
* header's argument for `git ls-files` over a tree walk applies here with one
|
|
191
|
+
* more clause: walking the tree for the untracked half would need those ignore
|
|
192
|
+
* rules reimplemented, and the copy would drift from `.gitignore` the first
|
|
193
|
+
* time a build directory was added.
|
|
194
|
+
*
|
|
195
|
+
* The order is git's worktree-traversal order, not a sorted order — every
|
|
196
|
+
* consumer of this list sorts before it renders (a file list in a report must
|
|
197
|
+
* not vary with git's traversal), the same discipline
|
|
198
|
+
* `../commands/check.mjs`'s `sortViolations` states for the tracked list.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} workspaceRoot
|
|
201
|
+
* @param {{ run?: typeof runProcess }} [io]
|
|
202
|
+
* @returns {string[]}
|
|
203
|
+
*/
|
|
204
|
+
export function listUntrackedFiles(workspaceRoot, { run = runProcess } = {}) {
|
|
205
|
+
const out = run("git", ["ls-files", "--others", "--exclude-standard", "-z"], workspaceRoot);
|
|
206
|
+
return out.split("\0").filter((path) => path !== "");
|
|
207
|
+
}
|
|
208
|
+
|
|
180
209
|
/**
|
|
181
210
|
* The `Workspace` the analysis contract defines — `{ root, projects, filesOf,
|
|
182
211
|
* readFile, tsConfig }` — plus the per-project file index it is built from,
|