@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
package/src/verdict.mjs
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
* The process's exit-code contract, and the one function that turns a run's
|
|
3
3
|
* counts into the verdict every format agrees on.
|
|
4
4
|
*
|
|
5
|
+
* Layer: the check lane — the process's exit codes, the coverage clauses a
|
|
6
|
+
* run that could not look needs, and `verdictFor`, the per-lint verdict the
|
|
7
|
+
* `check` command words its envelope and its exit from. Its counterpart,
|
|
8
|
+
* `./governance/verdict.mjs`, owns the verdict vocabulary and decision
|
|
9
|
+
* construction (`VERDICTS`, `isVerdict`, `verdictForStatus`, `fitnessVerdict`,
|
|
10
|
+
* `buildDecision`); the `buildDecision` import below is that layering made
|
|
11
|
+
* one-directional — the check lane calls governance, and governance never
|
|
12
|
+
* imports this file. Two near-collision pairs sit across that boundary, and
|
|
13
|
+
* the name decides which file a reader wants: `verdictFor` here folds a
|
|
14
|
+
* run's counts into status + exitCode, while governance's `verdictForStatus`
|
|
15
|
+
* looks one status up in its frozen map; `EXIT_FOR_STATUS` here is derived
|
|
16
|
+
* from this module's `EXIT`, while governance's `VERDICT_FOR_STATUS` is an
|
|
17
|
+
* independently frozen map. Exit-code, coverage and check-lane concerns live
|
|
18
|
+
* here; verdict vocabulary and decision construction live in
|
|
19
|
+
* `./governance/verdict.mjs`.
|
|
20
|
+
*
|
|
5
21
|
* Both sit here rather than in `../cli.mjs` because two callers need them and
|
|
6
22
|
* only one of the two is the CLI shell: `./commands/check.mjs` words its own
|
|
7
23
|
* `--format json` envelope's `status` and `exitCode` from `verdictFor`, and
|
|
@@ -17,6 +33,7 @@
|
|
|
17
33
|
*/
|
|
18
34
|
|
|
19
35
|
import { buildDecision } from "./governance/verdict.mjs";
|
|
36
|
+
import { describe, isPlainObject } from "./values.mjs";
|
|
20
37
|
|
|
21
38
|
export const EXIT = Object.freeze({
|
|
22
39
|
ok: 0,
|
|
@@ -88,6 +105,79 @@ export function coverageComplete({ unchecked, blindSpotCount, analyzed }) {
|
|
|
88
105
|
return unchecked === 0 && blindSpotCount === 0 && analyzed > 0;
|
|
89
106
|
}
|
|
90
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The count keys `verdictFor` folds, spelled as a roster because the
|
|
110
|
+
* destructure inside that function cannot be introspected. Every key is a
|
|
111
|
+
* non-negative integer or `undefined` (optional keys only); anything else —
|
|
112
|
+
* and any key not on this roster — is refused by the input latch below rather
|
|
113
|
+
* than destructured, because a misspelled key drops out of the destructure
|
|
114
|
+
* as `undefined` and `undefined > 0` is `false` in every lane: the missing
|
|
115
|
+
* count reads as zero, and a failing run can pass. The roster and the
|
|
116
|
+
* destructure are one list in two spellings; a key added to either without
|
|
117
|
+
* the other is a no-verdict on every `check` run, not a silent default.
|
|
118
|
+
*/
|
|
119
|
+
const REQUIRED_COUNT_KEYS = Object.freeze([
|
|
120
|
+
"violations",
|
|
121
|
+
"declaredEdgeFindings",
|
|
122
|
+
"goWorkDrift",
|
|
123
|
+
"tsconfigPathsDead",
|
|
124
|
+
"intentFindings",
|
|
125
|
+
"intentUnresolved",
|
|
126
|
+
"unchecked",
|
|
127
|
+
"analyzed",
|
|
128
|
+
"blindSpots",
|
|
129
|
+
]);
|
|
130
|
+
const OPTIONAL_COUNT_KEYS = Object.freeze([
|
|
131
|
+
"intentUnresolvedDecisionRefs",
|
|
132
|
+
"fitnessFail",
|
|
133
|
+
"fitnessUnknown",
|
|
134
|
+
"customRuleFail",
|
|
135
|
+
"customRuleUnknown",
|
|
136
|
+
]);
|
|
137
|
+
const KNOWN_COUNT_KEYS = new Set([...REQUIRED_COUNT_KEYS, ...OPTIONAL_COUNT_KEYS]);
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Whether a value is a count `verdictFor` can fold — a non-negative integer.
|
|
141
|
+
*
|
|
142
|
+
* @param {unknown} value
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
function isCount(value) {
|
|
146
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The input latch for `verdictFor`: names the first way `counts` is not a
|
|
151
|
+
* shape the fold can read, or `null` when it is. A returned problem becomes
|
|
152
|
+
* a no-verdict verdict, not a throw — the fold's contract is the same triple
|
|
153
|
+
* every verdict-bearing caller already consumes, and a malformed internal
|
|
154
|
+
* count is a fact about the run the caller reports the same way it reports a
|
|
155
|
+
* blind spot.
|
|
156
|
+
*
|
|
157
|
+
* @param {object} counts
|
|
158
|
+
* @returns {string|null}
|
|
159
|
+
*/
|
|
160
|
+
function countInputProblem(counts) {
|
|
161
|
+
if (!isPlainObject(counts)) {
|
|
162
|
+
return `the verdict fold refuses counts of ${describe(counts)} — the counts must be an object for any lane of the fold to read. This is a bug in archkeep, not a fact about the workspace.`;
|
|
163
|
+
}
|
|
164
|
+
const unknownKey = Object.keys(counts).find((key) => !KNOWN_COUNT_KEYS.has(key));
|
|
165
|
+
if (unknownKey !== undefined) {
|
|
166
|
+
return `the verdict fold refuses the unknown count key "${unknownKey}" — expected one of ${[...KNOWN_COUNT_KEYS].join(", ")}. A misspelled count key would read as 0 in every lane below and can flip a failing run into a pass. This is a bug in archkeep, not a fact about the workspace.`;
|
|
167
|
+
}
|
|
168
|
+
for (const key of REQUIRED_COUNT_KEYS) {
|
|
169
|
+
if (!isCount(counts[key])) {
|
|
170
|
+
return `the verdict fold refuses counts where the required key "${key}" is ${describe(counts[key])} — every count must be a non-negative integer, or a missing one reads as 0 in every lane below. This is a bug in archkeep, not a fact about the workspace.`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const key of OPTIONAL_COUNT_KEYS) {
|
|
174
|
+
if (counts[key] !== undefined && !isCount(counts[key])) {
|
|
175
|
+
return `the verdict fold refuses counts where the optional key "${key}" is ${describe(counts[key])} — every count must be a non-negative integer, or a malformed one reads as 0 in every lane below. This is a bug in archkeep, not a fact about the workspace.`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
91
181
|
/**
|
|
92
182
|
* The one place that turns a run's counts into the verdict every format
|
|
93
183
|
* agrees on. `runCheck` uses it for the process's exit code; `check` uses the
|
|
@@ -103,6 +193,12 @@ export function coverageComplete({ unchecked, blindSpotCount, analyzed }) {
|
|
|
103
193
|
* verify — is the case that must not read `ok`, because `ok` is read as
|
|
104
194
|
* "checked, and fine".
|
|
105
195
|
*
|
|
196
|
+
* The input latch runs before any lane: counts the fold cannot read are
|
|
197
|
+
* refused as a no-verdict whose single reason names the malformed input
|
|
198
|
+
* (`countInputProblem` above), never folded past as silent zeros. The refusal
|
|
199
|
+
* carries `coverageComplete: false` because the coverage counts are among the
|
|
200
|
+
* unread input — a coverage claim over them would be a second unread field.
|
|
201
|
+
*
|
|
106
202
|
* The `decision` is the canonical 4-state verb of the same verdict
|
|
107
203
|
* (`./governance/verdict.mjs`), built from the same counts so the envelope's
|
|
108
204
|
* `status` and its `decision.verdict` cannot disagree: `ok`→`pass`,
|
|
@@ -118,22 +214,37 @@ export function coverageComplete({ unchecked, blindSpotCount, analyzed }) {
|
|
|
118
214
|
* clean one. `decision.reason` joins it with the intent/fitness/custom
|
|
119
215
|
* clauses where the lane is no-verdict.
|
|
120
216
|
*/
|
|
121
|
-
export function verdictFor({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
217
|
+
export function verdictFor(counts) {
|
|
218
|
+
const problem = countInputProblem(counts);
|
|
219
|
+
if (problem !== null) {
|
|
220
|
+
return {
|
|
221
|
+
status: "no-verdict",
|
|
222
|
+
exitCode: EXIT_FOR_STATUS["no-verdict"],
|
|
223
|
+
reasons: [problem],
|
|
224
|
+
decision: buildDecision({
|
|
225
|
+
status: "no-verdict",
|
|
226
|
+
coverageComplete: false,
|
|
227
|
+
findings: 0,
|
|
228
|
+
reason: problem,
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const {
|
|
233
|
+
violations,
|
|
234
|
+
declaredEdgeFindings,
|
|
235
|
+
goWorkDrift,
|
|
236
|
+
tsconfigPathsDead,
|
|
237
|
+
intentFindings,
|
|
238
|
+
intentUnresolved,
|
|
239
|
+
intentUnresolvedDecisionRefs = 0,
|
|
240
|
+
unchecked,
|
|
241
|
+
analyzed,
|
|
242
|
+
blindSpots,
|
|
243
|
+
fitnessFail = 0,
|
|
244
|
+
fitnessUnknown = 0,
|
|
245
|
+
customRuleFail = 0,
|
|
246
|
+
customRuleUnknown = 0,
|
|
247
|
+
} = counts;
|
|
137
248
|
const coverageReasons = coverageIncompleteReasons({ unchecked, blindSpots, analyzed });
|
|
138
249
|
if (
|
|
139
250
|
violations > 0 ||
|