@ecoma-io/archkeep 0.21.0 → 0.22.1
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 +156 -66
- package/gate-attestation.mjs +23 -0
- package/package.json +3 -1
- package/src/analysis/analyze.mjs +6 -0
- package/src/analysis/contract.md +32 -5
- package/src/analysis/csharp.mjs +18 -0
- package/src/analysis/go.mjs +18 -0
- package/src/analysis/java.mjs +15 -0
- package/src/analysis/kotlin.mjs +15 -0
- package/src/analysis/python.mjs +25 -3
- package/src/analysis/rust.mjs +18 -0
- package/src/analysis/source-util.mjs +113 -0
- package/src/analysis/typescript.mjs +86 -5
- package/src/canonical.mjs +43 -25
- package/src/commands/README.md +63 -12
- package/src/commands/change-intent.mjs +25 -1
- package/src/commands/change.mjs +90 -40
- package/src/commands/check.mjs +65 -26
- package/src/commands/completeness.mjs +126 -19
- package/src/commands/context-command.mjs +13 -5
- package/src/commands/context.mjs +31 -4
- package/src/commands/coverage-verdict.mjs +191 -0
- package/src/commands/debt.mjs +18 -15
- package/src/commands/delta-classify.mjs +13 -18
- package/src/commands/delta-snapshot.mjs +13 -5
- package/src/commands/delta.mjs +95 -33
- package/src/commands/diff.mjs +31 -24
- package/src/commands/discover.mjs +70 -29
- package/src/commands/drift.mjs +21 -21
- package/src/commands/edge-constraints.mjs +47 -1
- package/src/commands/evaluation-primitives.mjs +194 -2
- package/src/commands/evolution.mjs +27 -10
- package/src/commands/explain.mjs +14 -13
- package/src/commands/fitness.mjs +20 -19
- package/src/commands/graph.mjs +29 -11
- package/src/commands/health.mjs +12 -5
- package/src/commands/history.mjs +41 -26
- package/src/commands/impact.mjs +17 -18
- package/src/commands/plan-context-command.mjs +10 -5
- package/src/commands/reconcile.mjs +14 -17
- package/src/commands/scenario-evaluation.mjs +93 -16
- package/src/commands/scenario.mjs +28 -18
- package/src/commands/waivers.mjs +36 -28
- package/src/governance/evolution-event.mjs +96 -9
- package/src/intent/intent-manifest.json +83 -39
- package/src/lsp/diagnose.mjs +12 -3
- package/src/report/discover-text.mjs +31 -9
- package/src/report/graph-text.mjs +25 -5
- package/src/report/json.mjs +32 -5
- package/src/report/text.mjs +82 -12
- package/src/verdict.mjs +78 -36
- package/src/verify-gate-attestation.mjs +323 -0
- package/src/workspace.mjs +126 -2
|
@@ -107,15 +107,102 @@ export function declarationDigest(intent) {
|
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* The escape character every identity spelling below uses, and the one
|
|
112
|
+
* function that applies it. A field that carries none of `\`, `>` or `:` and
|
|
113
|
+
* is not exactly `-` is returned byte-identical — the overwhelmingly common
|
|
114
|
+
* case, which is what keeps this escaping from rewriting the stored events of
|
|
115
|
+
* workspaces whose names never carried a delimiter (#627's fix is
|
|
116
|
+
* conditional by design; a wholesale re-encode on the `boundaryKey`
|
|
117
|
+
* pattern would change the persisted bytes of every workspace). A field that
|
|
118
|
+
* does carry one is escaped, so the delimiters that remain unescaped in an
|
|
119
|
+
* identity string are exactly the separators, and distinct field tuples can
|
|
120
|
+
* no longer join to the same string (#627). The sentinel `-` (`#628` writes
|
|
121
|
+
* it for an absent source project) escapes to `\-`, so a field that literally
|
|
122
|
+
* is `-` can no longer read as "absent" — `\-` in an identity string can only
|
|
123
|
+
* ever have come from field data.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} value One field of an identity string.
|
|
126
|
+
* @returns {string} The field, escaped iff escaping is needed.
|
|
127
|
+
*/
|
|
128
|
+
export function escapeIdentityField(value) {
|
|
129
|
+
if (!value.includes("\\") && !value.includes(">") && !value.includes(":") && value !== "-") {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
// Backslash first, so it never escapes an escape this pass itself wrote.
|
|
133
|
+
return value
|
|
134
|
+
.replaceAll("\\", "\\\\")
|
|
135
|
+
.replaceAll(">", "\\>")
|
|
136
|
+
.replaceAll(":", "\\:")
|
|
137
|
+
.replace(/^-$/u, "\\-");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The identity string of one graph edge, in the canonical spelling
|
|
142
|
+
* `source>target:type` — the `(source, target, type)` identity design §1
|
|
143
|
+
* names. The ONE spelling the evolution events' `observed.edges` and
|
|
144
|
+
* `affected.boundaries` use: this module owns it, and `classifyEvolution`
|
|
145
|
+
* maps every edge it is handed through this function, so there is exactly
|
|
146
|
+
* one definition of "same edge" and no second spelling to drift. Fields are
|
|
147
|
+
* escaped through `escapeIdentityField`, so the unescaped `>` and `:` in the
|
|
148
|
+
* result are the separators and two distinct triples never join to one
|
|
149
|
+
* string (#627) — while a triple with no delimiter in any field spells
|
|
150
|
+
* exactly what earlier versions spelled, byte for byte.
|
|
151
|
+
*
|
|
152
|
+
* @param {{source: string, target: string, type: string}} edge
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
export function edgeEvolutionIdentity({ source, target, type }) {
|
|
156
|
+
return `${escapeIdentityField(source)}>${escapeIdentityField(target)}:${escapeIdentityField(type)}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The one accepted input shape for an `observed.edges` entry: the raw
|
|
161
|
+
* `{source, target, type}` triple. A caller handing over a ready-made string
|
|
162
|
+
* would be choosing a second spelling of "same edge", so a string is refused
|
|
163
|
+
* loudly rather than accepted as one shape more — the identity string is this
|
|
164
|
+
* module's output, never its input.
|
|
165
|
+
*
|
|
166
|
+
* @param {unknown} entry
|
|
167
|
+
* @returns {string}
|
|
168
|
+
*/
|
|
169
|
+
function evolutionBoundary(entry) {
|
|
170
|
+
if (
|
|
171
|
+
typeof entry !== "object" ||
|
|
172
|
+
entry === null ||
|
|
173
|
+
!("source" in entry) ||
|
|
174
|
+
typeof entry.source !== "string" ||
|
|
175
|
+
entry.source === "" ||
|
|
176
|
+
!("target" in entry) ||
|
|
177
|
+
typeof entry.target !== "string" ||
|
|
178
|
+
entry.target === "" ||
|
|
179
|
+
!("type" in entry) ||
|
|
180
|
+
typeof entry.type !== "string"
|
|
181
|
+
) {
|
|
182
|
+
throw new TypeError(
|
|
183
|
+
"classifyEvolution: observed.edges entries must be {source, target, type} triples — " +
|
|
184
|
+
"the identity string is classifyEvolution's own output spelling, never an input",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
// The guard above has verified every property; the annotation only states
|
|
188
|
+
// what it proved.
|
|
189
|
+
return edgeEvolutionIdentity(
|
|
190
|
+
/** @type {{source: string, target: string, type: string}} */ (entry),
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
110
194
|
/**
|
|
111
195
|
* @typedef {object} EvolutionEvidence
|
|
112
196
|
* @property {{projects?: {added: string[], removed: string[], changed: string[]},
|
|
113
|
-
* edges?: {added: string
|
|
197
|
+
* edges?: {added: {source: string, target: string, type: string}[],
|
|
198
|
+
* removed: {source: string, target: string, type: string}[]},
|
|
114
199
|
* policyChanged?: boolean|null, policyOneSided?: boolean,
|
|
115
200
|
* provenanceChanged?: boolean|null}} [observed]
|
|
116
|
-
* The structural diff between base and head: project names and edge
|
|
117
|
-
*
|
|
118
|
-
*
|
|
201
|
+
* The structural diff between base and head: project names and raw edge
|
|
202
|
+
* triples that were added, removed, or changed. The triples are mapped
|
|
203
|
+
* through `edgeEvolutionIdentity` here — the identity spelling is this
|
|
204
|
+
* module's own, so `affected.boundaries` comes out as identity strings
|
|
205
|
+
* whichever shape the caller held. Empty by default. `policyChanged` — whether the policy fingerprint changed
|
|
119
206
|
* between base and head; `null` is "could not be compared": exactly one side
|
|
120
207
|
* records the policy (`policyOneSided: true`) or neither does
|
|
121
208
|
* (both-absent). `true` is a disclosure, never a refusal. `policyOneSided`
|
|
@@ -197,9 +284,9 @@ export function declarationDigest(intent) {
|
|
|
197
284
|
* absent (`null`) ⇒ NOT asserted, note added |
|
|
198
285
|
*
|
|
199
286
|
* The `affected` identities are derived from the same signals, never from a
|
|
200
|
-
* second opinion: changed project names, changed
|
|
201
|
-
*
|
|
202
|
-
* ids whose lineage moved.
|
|
287
|
+
* second opinion: changed project names, the changed edges under the one
|
|
288
|
+
* identity spelling (`edgeEvolutionIdentity`), the constraint/intent rows
|
|
289
|
+
* whose verdict was not `pass`/`matched`, and the ADR ids whose lineage moved.
|
|
203
290
|
*
|
|
204
291
|
* @param {EvolutionEvidence} [input]
|
|
205
292
|
* @returns {EvolutionClassification}
|
|
@@ -211,8 +298,8 @@ export function classifyEvolution(input = {}) {
|
|
|
211
298
|
const addedProjects = projects.added ?? [];
|
|
212
299
|
const removedProjects = projects.removed ?? [];
|
|
213
300
|
const changedProjects = projects.changed ?? [];
|
|
214
|
-
const addedEdges = edges.added ?? [];
|
|
215
|
-
const removedEdges = edges.removed ?? [];
|
|
301
|
+
const addedEdges = (edges.added ?? []).map(evolutionBoundary);
|
|
302
|
+
const removedEdges = (edges.removed ?? []).map(evolutionBoundary);
|
|
216
303
|
const structureChanged =
|
|
217
304
|
addedProjects.length +
|
|
218
305
|
removedProjects.length +
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 2,
|
|
3
3
|
"generatedAt": "v1.0.0-hardening",
|
|
4
|
-
"description": "Machine-readable manifest declaring every v1.0 intent and the evidence that supports it. A contract is 'proven' only when at least one evidence entry is an executable proof (behavioral-test, e2e-test, or architecture-test). Source-evidence and documentation support the claim but cannot prove it alone.",
|
|
4
|
+
"description": "Machine-readable manifest declaring every v1.0 intent and the evidence that supports it. A contract is 'proven' only when at least one evidence entry is an executable proof (behavioral-test, e2e-test, or architecture-test). Source-evidence and documentation support the claim but cannot prove it alone. Every evidence entry carries the sha256 of the artifact it names, and the gate recomputes each digest, so evidence is content-addressed: an artifact that changed after its claim was certified is a loud mismatch, not a silent swap.",
|
|
5
5
|
"evidenceTaxonomy": {
|
|
6
6
|
"behavioral-test": "A unit or integration test that imports and calls the code, asserting on runtime behavior (not source text). Proves the intent is regression-protected.",
|
|
7
7
|
"e2e-test": "A CLI subprocess test that exercises the real binary against a consumer workspace. Proves the intent holds end-to-end.",
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
{
|
|
20
20
|
"type": "architecture-test",
|
|
21
21
|
"path": "src/conformance/boundary.test.mjs",
|
|
22
|
-
"assertion": "SHIPPED_PACKAGES allow-list prevents unapproved dependencies; specifiersIn() walk catches all imports including createRequire; no provider import found in core layers"
|
|
22
|
+
"assertion": "SHIPPED_PACKAGES allow-list prevents unapproved dependencies; specifiersIn() walk catches all imports including createRequire; no provider import found in core layers",
|
|
23
|
+
"sha256": "12d4e179f88de2622a41e56f42b2bab1c88a677ec2232430752138c57ba5fb18"
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
26
|
"type": "source-evidence",
|
|
26
27
|
"path": "src/commands/context.mjs",
|
|
27
|
-
"assertion": "Only commands/context.mjs imports providers — the designated orchestration layer"
|
|
28
|
+
"assertion": "Only commands/context.mjs imports providers — the designated orchestration layer",
|
|
29
|
+
"sha256": "7f566c06afbb175337529bd9bdc42465f8849498b932abf6a77fe0290a0cd13c"
|
|
28
30
|
}
|
|
29
31
|
],
|
|
30
32
|
"status": "proven"
|
|
@@ -37,17 +39,20 @@
|
|
|
37
39
|
{
|
|
38
40
|
"type": "behavioral-test",
|
|
39
41
|
"path": "src/report/json.test.mjs",
|
|
40
|
-
"assertion": "JSON envelope throws on status=ok + incomplete coverage, status/exitCode disagreement, coverage.complete/notAnalyzed disagreement"
|
|
42
|
+
"assertion": "JSON envelope throws on status=ok + incomplete coverage, status/exitCode disagreement, coverage.complete/notAnalyzed disagreement",
|
|
43
|
+
"sha256": "a371d36b89b10c5a4bda4d9e9a2e3bde114256c1377eb95699c9f0f195aabc4a"
|
|
41
44
|
},
|
|
42
45
|
{
|
|
43
46
|
"type": "source-evidence",
|
|
44
47
|
"path": "src/lsp/diagnose.mjs",
|
|
45
|
-
"assertion": "analyzed:false on every non-verdict path; empty diagnostic list only from two named places"
|
|
48
|
+
"assertion": "analyzed:false on every non-verdict path; empty diagnostic list only from two named places — plus a document whose only positioned failure is an external disclosure, which was judged and so is not published (#603)",
|
|
49
|
+
"sha256": "72196def14055475ccb23284a3c5d34167535a506985b26fb95af097545cc740"
|
|
46
50
|
},
|
|
47
51
|
{
|
|
48
52
|
"type": "source-evidence",
|
|
49
53
|
"path": "cli.mjs",
|
|
50
|
-
"assertion": "Exit codes 0/1/3 distinguish clean/findings/cannot-look"
|
|
54
|
+
"assertion": "Exit codes 0/1/3 distinguish clean/findings/cannot-look",
|
|
55
|
+
"sha256": "81abe8243660b3cca913ee903c1fc0bfcdb6a240640c5302f18848e6a9473fe6"
|
|
51
56
|
}
|
|
52
57
|
],
|
|
53
58
|
"status": "proven"
|
|
@@ -60,17 +65,26 @@
|
|
|
60
65
|
{
|
|
61
66
|
"type": "behavioral-test",
|
|
62
67
|
"path": "src/intent/intent.test.mjs",
|
|
63
|
-
"assertion": "analysis output conforms to the frozen contract schema (no extra verdict/policy fields); analysis output is invariant under project tag changes
|
|
68
|
+
"assertion": "analysis output conforms to the frozen contract schema (no extra verdict/policy fields); analysis output is invariant under project tag changes (three workspaces differing only in project tag fields compared byte-for-byte)",
|
|
69
|
+
"sha256": "6ed86cb5e7af94b56fa2af3d2da5d45d58937fbbf316105da0224d0060e88582"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"type": "architecture-test",
|
|
73
|
+
"path": "src/intent/intent.test.mjs",
|
|
74
|
+
"assertion": "the gate walks every production analysis module and fails when judging vocabulary (judge/forbid/permit/allow/ban) appears in code",
|
|
75
|
+
"sha256": "6ed86cb5e7af94b56fa2af3d2da5d45d58937fbbf316105da0224d0060e88582"
|
|
64
76
|
},
|
|
65
77
|
{
|
|
66
78
|
"type": "source-evidence",
|
|
67
79
|
"path": "src/analysis/contract.md",
|
|
68
|
-
"assertion": "Analysis record is a superset of a graph edge — 5 of 15 violations decided on raw specifier"
|
|
80
|
+
"assertion": "Analysis record is a superset of a graph edge — 5 of 15 violations decided on raw specifier",
|
|
81
|
+
"sha256": "56d2db7816ba5ffc5fbe9e299f7d5e270a857b42eb54bd3bea7c05da17be595b"
|
|
69
82
|
},
|
|
70
83
|
{
|
|
71
84
|
"type": "source-evidence",
|
|
72
85
|
"path": "AGENTS.md",
|
|
73
|
-
"assertion": "src/graph/ is a lossy view of analysis, on purpose; src/analysis/ never judges"
|
|
86
|
+
"assertion": "src/graph/ is a lossy view of analysis, on purpose; src/analysis/ never judges",
|
|
87
|
+
"sha256": "3209154935cc1463127721203da74dc8bf8ba40258b182c5faa7345946611e38"
|
|
74
88
|
}
|
|
75
89
|
],
|
|
76
90
|
"status": "proven"
|
|
@@ -83,17 +97,20 @@
|
|
|
83
97
|
{
|
|
84
98
|
"type": "e2e-test",
|
|
85
99
|
"path": "e2e/determinism.e2e.mjs",
|
|
86
|
-
"assertion": "Graph, check, impact run twice — byte-identical JSON output"
|
|
100
|
+
"assertion": "Graph, check, impact run twice — byte-identical JSON output",
|
|
101
|
+
"sha256": "90c0f2a62a111013990ff96ec07042e961c1aae50710c597192657cc270df5ec"
|
|
87
102
|
},
|
|
88
103
|
{
|
|
89
104
|
"type": "source-evidence",
|
|
90
105
|
"path": "src/commands/graph.mjs",
|
|
91
|
-
"assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped; SCHEMA_VERSION = 2"
|
|
106
|
+
"assertion": "Plain string comparison, never localeCompare; INTERNAL_DATA_FIELDS stripped; SCHEMA_VERSION = 2",
|
|
107
|
+
"sha256": "25ece527ff5ea847f5863314a12c50c95a80f995d36d3aeb756ca022dd72f283"
|
|
92
108
|
},
|
|
93
109
|
{
|
|
94
110
|
"type": "source-evidence",
|
|
95
111
|
"path": "src/commands/graph.mjs",
|
|
96
|
-
"assertion": "computePolicyFingerprint produces SHA-256 of canonicalized policy"
|
|
112
|
+
"assertion": "computePolicyFingerprint produces SHA-256 of canonicalized policy",
|
|
113
|
+
"sha256": "25ece527ff5ea847f5863314a12c50c95a80f995d36d3aeb756ca022dd72f283"
|
|
97
114
|
}
|
|
98
115
|
],
|
|
99
116
|
"status": "proven"
|
|
@@ -106,12 +123,14 @@
|
|
|
106
123
|
{
|
|
107
124
|
"type": "e2e-test",
|
|
108
125
|
"path": "e2e/diff.e2e.mjs",
|
|
109
|
-
"assertion": "Invalid baseline schemaVersion exits 3; incompatible version exits 3"
|
|
126
|
+
"assertion": "Invalid baseline schemaVersion exits 3; incompatible version exits 3",
|
|
127
|
+
"sha256": "7892a6dc56f5e1ec725fd6d029bba76bc493d8c34790f346e15af0d7d0dbbdc1"
|
|
110
128
|
},
|
|
111
129
|
{
|
|
112
130
|
"type": "source-evidence",
|
|
113
131
|
"path": "src/commands/diff.mjs",
|
|
114
|
-
"assertion": "parseBaseline validates schemaVersion; refuses unknown versions"
|
|
132
|
+
"assertion": "parseBaseline validates schemaVersion; refuses unknown versions",
|
|
133
|
+
"sha256": "6190013ec55eeb7f1a9e4cdd910c7be73bfb1fa87051bdeace7c9581c6a18426"
|
|
115
134
|
}
|
|
116
135
|
],
|
|
117
136
|
"status": "proven"
|
|
@@ -124,12 +143,14 @@
|
|
|
124
143
|
{
|
|
125
144
|
"type": "e2e-test",
|
|
126
145
|
"path": "e2e/diff.e2e.mjs",
|
|
127
|
-
"assertion": "Self-baseline exits 0; added/removed edges reported; policy mismatch warned; rule-impact with config"
|
|
146
|
+
"assertion": "Self-baseline exits 0; added/removed edges reported; policy mismatch warned; rule-impact with config",
|
|
147
|
+
"sha256": "7892a6dc56f5e1ec725fd6d029bba76bc493d8c34790f346e15af0d7d0dbbdc1"
|
|
128
148
|
},
|
|
129
149
|
{
|
|
130
150
|
"type": "source-evidence",
|
|
131
151
|
"path": "src/commands/diff.mjs",
|
|
132
|
-
"assertion": "computeDiff returns structural diff; policyMismatch detected via fingerprint; computeRuleImpact for depConstraints-only context"
|
|
152
|
+
"assertion": "computeDiff returns structural diff; policyMismatch detected via fingerprint; computeRuleImpact for depConstraints-only context",
|
|
153
|
+
"sha256": "6190013ec55eeb7f1a9e4cdd910c7be73bfb1fa87051bdeace7c9581c6a18426"
|
|
133
154
|
}
|
|
134
155
|
],
|
|
135
156
|
"status": "proven"
|
|
@@ -142,12 +163,14 @@
|
|
|
142
163
|
{
|
|
143
164
|
"type": "e2e-test",
|
|
144
165
|
"path": "e2e/determinism.e2e.mjs",
|
|
145
|
-
"assertion": "Impact --format json produces identical output on two runs"
|
|
166
|
+
"assertion": "Impact --format json produces identical output on two runs",
|
|
167
|
+
"sha256": "90c0f2a62a111013990ff96ec07042e961c1aae50710c597192657cc270df5ec"
|
|
146
168
|
},
|
|
147
169
|
{
|
|
148
170
|
"type": "behavioral-test",
|
|
149
171
|
"path": "src/commands/impact.test.mjs",
|
|
150
|
-
"assertion": "Sorts results using plain string comparison (never localeCompare)"
|
|
172
|
+
"assertion": "Sorts results using plain string comparison (never localeCompare)",
|
|
173
|
+
"sha256": "b188d7001d146c203be490727414f0c585d8e589f10e3a9add40a34b5aedd532"
|
|
151
174
|
}
|
|
152
175
|
],
|
|
153
176
|
"status": "proven"
|
|
@@ -160,17 +183,20 @@
|
|
|
160
183
|
{
|
|
161
184
|
"type": "behavioral-test",
|
|
162
185
|
"path": "src/commands/context-command.test.mjs",
|
|
163
|
-
"assertion": "Returns tags, matched constraints, per-edge violations; coverage.notes warns about depConstraints-only scope"
|
|
186
|
+
"assertion": "Returns tags, matched constraints, per-edge violations; coverage.notes warns about depConstraints-only scope",
|
|
187
|
+
"sha256": "eccd31e3057bbb0cb45785e0047e0d71e83dbbc00703ee231f085cf52fb76db8"
|
|
164
188
|
},
|
|
165
189
|
{
|
|
166
190
|
"type": "e2e-test",
|
|
167
191
|
"path": "e2e/context.e2e.mjs",
|
|
168
|
-
"assertion": "Native consumer: tags, constraints, coverage.notes verified; Moon consumer: context E2E"
|
|
192
|
+
"assertion": "Native consumer: tags, constraints, coverage.notes verified; Moon consumer: context E2E",
|
|
193
|
+
"sha256": "336994ea66e2b7b7ff1d374e3127b478d530508b8c8e5ef927805c5aa11b6cb8"
|
|
169
194
|
},
|
|
170
195
|
{
|
|
171
196
|
"type": "documentation",
|
|
172
197
|
"path": "../../docs/concepts/agentic-development.md",
|
|
173
|
-
"assertion": "Section 'What context and impact do not check' warns agents about the semantic gap"
|
|
198
|
+
"assertion": "Section 'What context and impact do not check' warns agents about the semantic gap",
|
|
199
|
+
"sha256": "a42bca1e927a59d28160eb947a285c20d1a6cc9633a69c91a45e9d46bbe1686b"
|
|
174
200
|
}
|
|
175
201
|
],
|
|
176
202
|
"status": "proven"
|
|
@@ -183,12 +209,14 @@
|
|
|
183
209
|
{
|
|
184
210
|
"type": "behavioral-test",
|
|
185
211
|
"path": "src/commands/explain.test.mjs",
|
|
186
|
-
"assertion": "Returns all violations at a site; unresolvable site returns unresolvable:true with reason"
|
|
212
|
+
"assertion": "Returns all violations at a site; unresolvable site returns unresolvable:true with reason",
|
|
213
|
+
"sha256": "017581d978f277fadecedd6a4a0a4befca3743dbf2b084e1192792a1891466ad"
|
|
187
214
|
},
|
|
188
215
|
{
|
|
189
216
|
"type": "e2e-test",
|
|
190
217
|
"path": "e2e/explain.e2e.mjs",
|
|
191
|
-
"assertion": "Native and Moon consumers: specifier, sourceProject, targetProject, violations verified"
|
|
218
|
+
"assertion": "Native and Moon consumers: specifier, sourceProject, targetProject, violations verified",
|
|
219
|
+
"sha256": "857e198249c0614af643f784d985ae782ab8dfa9906f92f44193e2570ee0c343"
|
|
192
220
|
}
|
|
193
221
|
],
|
|
194
222
|
"status": "proven"
|
|
@@ -201,37 +229,44 @@
|
|
|
201
229
|
{
|
|
202
230
|
"type": "behavioral-test",
|
|
203
231
|
"path": "src/intent/intent.test.mjs",
|
|
204
|
-
"assertion": "depConstraints verdicts from judgeEdge agree with evaluate; explain includes the same violations as evaluate at a given site
|
|
232
|
+
"assertion": "depConstraints verdicts from judgeEdge agree with evaluate in both directions (violating edge found by both, legal edge reported by neither); explain includes the same violations as evaluate at a given site",
|
|
233
|
+
"sha256": "6ed86cb5e7af94b56fa2af3d2da5d45d58937fbbf316105da0224d0060e88582"
|
|
205
234
|
},
|
|
206
235
|
{
|
|
207
236
|
"type": "behavioral-test",
|
|
208
237
|
"path": "src/commands/context-command.test.mjs",
|
|
209
|
-
"assertion": "Test verifies coverage.notes contains depConstraints warning"
|
|
238
|
+
"assertion": "Test verifies coverage.notes contains depConstraints warning",
|
|
239
|
+
"sha256": "eccd31e3057bbb0cb45785e0047e0d71e83dbbc00703ee231f085cf52fb76db8"
|
|
210
240
|
},
|
|
211
241
|
{
|
|
212
242
|
"type": "e2e-test",
|
|
213
243
|
"path": "e2e/context.e2e.mjs",
|
|
214
|
-
"assertion": "E2E verifies coverage.notes in JSON envelope"
|
|
244
|
+
"assertion": "E2E verifies coverage.notes in JSON envelope",
|
|
245
|
+
"sha256": "336994ea66e2b7b7ff1d374e3127b478d530508b8c8e5ef927805c5aa11b6cb8"
|
|
215
246
|
},
|
|
216
247
|
{
|
|
217
248
|
"type": "source-evidence",
|
|
218
249
|
"path": "src/commands/context-command.mjs",
|
|
219
|
-
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)"
|
|
250
|
+
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
|
|
251
|
+
"sha256": "72526cdaf038da4d9a50b33ab3cb30828713e62bbb891f4e2e51e008a403338e"
|
|
220
252
|
},
|
|
221
253
|
{
|
|
222
254
|
"type": "source-evidence",
|
|
223
255
|
"path": "src/commands/impact.mjs",
|
|
224
|
-
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)"
|
|
256
|
+
"assertion": "coverage.notes warns that per-edge violations cover only depConstraints (3 of 15 violation types)",
|
|
257
|
+
"sha256": "38cff166a8944860c51a73316447ce789898751489d783e75fd6befbfd30be3c"
|
|
225
258
|
},
|
|
226
259
|
{
|
|
227
260
|
"type": "source-evidence",
|
|
228
261
|
"path": "src/commands/diff.mjs",
|
|
229
|
-
"assertion": "coverage.notes warns when ruleImpact is computed (depConstraints only, 3 of 15)"
|
|
262
|
+
"assertion": "coverage.notes warns when ruleImpact is computed (depConstraints only, 3 of 15)",
|
|
263
|
+
"sha256": "6190013ec55eeb7f1a9e4cdd910c7be73bfb1fa87051bdeace7c9581c6a18426"
|
|
230
264
|
},
|
|
231
265
|
{
|
|
232
266
|
"type": "documentation",
|
|
233
267
|
"path": "../../docs/concepts/agentic-development.md",
|
|
234
|
-
"assertion": "Section warns agents: violations:[] means allowed by constraint table, not free of all boundary violations"
|
|
268
|
+
"assertion": "Section warns agents: violations:[] means allowed by constraint table, not free of all boundary violations",
|
|
269
|
+
"sha256": "a42bca1e927a59d28160eb947a285c20d1a6cc9633a69c91a45e9d46bbe1686b"
|
|
235
270
|
}
|
|
236
271
|
],
|
|
237
272
|
"status": "proven"
|
|
@@ -244,12 +279,14 @@
|
|
|
244
279
|
{
|
|
245
280
|
"type": "e2e-test",
|
|
246
281
|
"path": "e2e/determinism.e2e.mjs",
|
|
247
|
-
"assertion": "Graph, check, impact: two runs produce byte-identical JSON"
|
|
282
|
+
"assertion": "Graph, check, impact: two runs produce byte-identical JSON",
|
|
283
|
+
"sha256": "90c0f2a62a111013990ff96ec07042e961c1aae50710c597192657cc270df5ec"
|
|
248
284
|
},
|
|
249
285
|
{
|
|
250
286
|
"type": "source-evidence",
|
|
251
287
|
"path": "src/commands/graph.mjs",
|
|
252
|
-
"assertion": "Plain string comparison throughout; never localeCompare"
|
|
288
|
+
"assertion": "Plain string comparison throughout; never localeCompare",
|
|
289
|
+
"sha256": "25ece527ff5ea847f5863314a12c50c95a80f995d36d3aeb756ca022dd72f283"
|
|
253
290
|
}
|
|
254
291
|
],
|
|
255
292
|
"status": "proven"
|
|
@@ -262,22 +299,26 @@
|
|
|
262
299
|
{
|
|
263
300
|
"type": "e2e-test",
|
|
264
301
|
"path": "e2e/parity.e2e.mjs",
|
|
265
|
-
"assertion": "Nx vs Native: project names, edge source/target/type, violation rule IDs, envelope structure"
|
|
302
|
+
"assertion": "Nx vs Native: project names, edge source/target/type, violation rule IDs, envelope structure",
|
|
303
|
+
"sha256": "106f59315e4661ae67f5c1e44895d56f7f7562eacd4c44101a9ed4b0e56f774e"
|
|
266
304
|
},
|
|
267
305
|
{
|
|
268
306
|
"type": "e2e-test",
|
|
269
307
|
"path": "e2e/moon.e2e.mjs",
|
|
270
|
-
"assertion": "Moon: check, graph, diff, impact, explain, context verified against Moon provider (conditional on moon CLI availability)"
|
|
308
|
+
"assertion": "Moon: check, graph, diff, impact, explain, context verified against Moon provider (conditional on moon CLI availability)",
|
|
309
|
+
"sha256": "8293a65ff407ea43a3985c742ae6333970ec3b0400f3df46189793a3dc87af6b"
|
|
271
310
|
},
|
|
272
311
|
{
|
|
273
312
|
"type": "source-evidence",
|
|
274
313
|
"path": "src/providers/moon.mjs",
|
|
275
|
-
"assertion": "inferWorkspaceLayout returns null for partial layouts — same all-or-nothing contract as Nx and Native"
|
|
314
|
+
"assertion": "inferWorkspaceLayout returns null for partial layouts — same all-or-nothing contract as Nx and Native",
|
|
315
|
+
"sha256": "232dd8e2cbe70c9149ba3a75a03dcebe0db355894c280964ebbf6a5a536b5567"
|
|
276
316
|
},
|
|
277
317
|
{
|
|
278
318
|
"type": "behavioral-test",
|
|
279
319
|
"path": "src/providers/moon.test.mjs",
|
|
280
|
-
"assertion": "Partial layout (apps-only, libs-only) returns undefined workspaceLayout"
|
|
320
|
+
"assertion": "Partial layout (apps-only, libs-only) returns undefined workspaceLayout",
|
|
321
|
+
"sha256": "084a0061b3236e680274d837fb2f12d27dd4b5a379040157f7afe2be5088e8ac"
|
|
281
322
|
}
|
|
282
323
|
],
|
|
283
324
|
"status": "proven"
|
|
@@ -290,17 +331,20 @@
|
|
|
290
331
|
{
|
|
291
332
|
"type": "behavioral-test",
|
|
292
333
|
"path": "src/commands/drift.test.mjs",
|
|
293
|
-
"assertion": "driftCommand returns status ok with observed facts and findings; refuses incomplete coverage, absent intent, and unregistered-plugin incomplete graph; buildObserved counts implicit edges separately"
|
|
334
|
+
"assertion": "driftCommand returns status ok with observed facts and findings; refuses incomplete coverage, absent intent, and unregistered-plugin incomplete graph; buildObserved counts implicit edges separately",
|
|
335
|
+
"sha256": "dbb0dfa1ecce9123ad0a38cdd6bc97e6645a47cba78496357318375db952e43a"
|
|
294
336
|
},
|
|
295
337
|
{
|
|
296
338
|
"type": "behavioral-test",
|
|
297
339
|
"path": "src/report/drift-text.test.mjs",
|
|
298
|
-
"assertion": "formatDriftReport states comparison facts even when clean (no-drift is a claim about coverage); groups findings by rule in taxonomy order; byte-identical determinism"
|
|
340
|
+
"assertion": "formatDriftReport states comparison facts even when clean (no-drift is a claim about coverage); groups findings by rule in taxonomy order; byte-identical determinism",
|
|
341
|
+
"sha256": "767d395c40cb0fa61f19ec06d587708e4ba065323bb1ee170254365847b49cc5"
|
|
299
342
|
},
|
|
300
343
|
{
|
|
301
344
|
"type": "e2e-test",
|
|
302
345
|
"path": "e2e/drift.e2e.mjs",
|
|
303
|
-
"assertion": "Through the installed CLI: drift exits 0 on a matching tree and names fingerprint/rows; reports dependencyForbidden and projectMissing; JSON envelope carries findings; malformed intent exits 3 for both drift and check"
|
|
346
|
+
"assertion": "Through the installed CLI: drift exits 0 on a matching tree and names fingerprint/rows; reports dependencyForbidden and projectMissing; JSON envelope carries findings; malformed intent exits 3 for both drift and check",
|
|
347
|
+
"sha256": "1e060ba0f02497259fdde2481dc59bd74bf6658f2fc354100de4f89ff6ffb926"
|
|
304
348
|
}
|
|
305
349
|
],
|
|
306
350
|
"status": "proven"
|
package/src/lsp/diagnose.mjs
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
* places outside the boundary system entirely.
|
|
43
43
|
*/
|
|
44
44
|
import { analyzeFile } from "../analysis/analyze.mjs";
|
|
45
|
-
import { projectOwning } from "../analysis/source-util.mjs";
|
|
45
|
+
import { isExternalSiteFailure, projectOwning } from "../analysis/source-util.mjs";
|
|
46
46
|
import { declaredEdgeViolationsForCheck } from "../commands/edge-constraints.mjs";
|
|
47
47
|
import { evaluate } from "../rules/index.mjs";
|
|
48
48
|
|
|
@@ -97,10 +97,19 @@ export function diagnoseDocument({ sourceFile, text, index, config }) {
|
|
|
97
97
|
|
|
98
98
|
// Recorded failures come next, and they are published whether or not the
|
|
99
99
|
// rule pass below succeeds: they are the part of the file that was NOT
|
|
100
|
-
// judged, and a reader needs that before they read what was.
|
|
100
|
+
// judged, and a reader needs that before they read what was. The external
|
|
101
|
+
// class is not that part (`isExternalSiteFailure`): a bare coordinate that
|
|
102
|
+
// resolves to the dependency universe was judged — resolved external,
|
|
103
|
+
// disclosed in the run's blind-spot rows, excluded from the withholding
|
|
104
|
+
// count — so a warning on it would say "not checked" about a site the
|
|
105
|
+
// verdict below covers, and one per third-party import would be a wall of
|
|
106
|
+
// warnings a reader rightly learns to ignore (#603). The workspace-surface
|
|
107
|
+
// and whole-file classes keep publishing.
|
|
101
108
|
const diagnostics = [
|
|
102
109
|
...prelude,
|
|
103
|
-
...analysis.failures
|
|
110
|
+
...analysis.failures
|
|
111
|
+
.filter((failure) => !isExternalSiteFailure(failure))
|
|
112
|
+
.map((failure) => failureDiagnostic(failure, lines)),
|
|
104
113
|
];
|
|
105
114
|
|
|
106
115
|
// The engine derives its evidence index from exactly the records it is
|
|
@@ -4,15 +4,27 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The coverage claim sits ABOVE everything — the reader knows whether the
|
|
6
6
|
* observations are complete before reading any entry, exactly like
|
|
7
|
-
* `./graph-text.mjs`'s report.
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* `./graph-text.mjs`'s report. Under an incomplete claim sit the reason
|
|
8
|
+
* clauses the run withheld the verdict over, worded by
|
|
9
|
+
* `../verdict.mjs`'s `coverageIncompleteReasons` and rendered through
|
|
10
|
+
* `./text.mjs`'s `formatCoverageIncomplete` — the same clauses, in the
|
|
11
|
+
* same `⚠` rendering, `check`'s text report prints, so a terminal reader is
|
|
12
|
+
* told why the verdict is withheld whichever face ran. A zero-analysis run
|
|
13
|
+
* is the case that needs this: its `notAnalyzed` list is empty, so a
|
|
14
|
+
* count-bearing headline would blame zero failures for an incomplete
|
|
15
|
+
* discovery (#619).
|
|
16
|
+
*
|
|
17
|
+
* The proposal, when present, is rendered below the observations with the
|
|
18
|
+
* proposal-only banner (`proposed`, `not authoritative`) repeated on every
|
|
19
|
+
* line of every candidate, so a reader who scans the report cannot mistake a
|
|
20
|
+
* candidate for a decision.
|
|
11
21
|
*
|
|
12
22
|
* This module decides nothing. A formatter that filtered would be a rule
|
|
13
23
|
* wearing a formatter's name (`../README.md`).
|
|
14
24
|
*/
|
|
15
25
|
|
|
26
|
+
import { formatCoverageIncomplete } from "./text.mjs";
|
|
27
|
+
|
|
16
28
|
/** The three confidence markers, in the order the legend prints them. */
|
|
17
29
|
const CONFIDENCE_ORDER = ["high", "medium", "low"];
|
|
18
30
|
|
|
@@ -96,10 +108,16 @@ function formatRule(item) {
|
|
|
96
108
|
*
|
|
97
109
|
* @param {{discovery: {projects: object[], edges: object[], tags: string[]},
|
|
98
110
|
* proposal: object|null,
|
|
99
|
-
* coverage: object
|
|
111
|
+
* coverage: object,
|
|
112
|
+
* coverageIncomplete?: string[]}} input
|
|
113
|
+
* `coverageIncomplete` is the withheld-verdict clause list
|
|
114
|
+
* (`../verdict.mjs`'s `coverageIncompleteReasons`, handed through
|
|
115
|
+
* `../commands/discover.mjs`) — rendered below the incomplete headline,
|
|
116
|
+
* empty exactly when the discovery is complete, and optional because a
|
|
117
|
+
* complete discovery carries no clauses to render.
|
|
100
118
|
* @returns {string}
|
|
101
119
|
*/
|
|
102
|
-
export function formatDiscoverReport({ discovery, proposal, coverage }) {
|
|
120
|
+
export function formatDiscoverReport({ discovery, proposal, coverage, coverageIncomplete }) {
|
|
103
121
|
const sections = [];
|
|
104
122
|
|
|
105
123
|
const inspected =
|
|
@@ -110,11 +128,15 @@ export function formatDiscoverReport({ discovery, proposal, coverage }) {
|
|
|
110
128
|
if (coverage.complete) {
|
|
111
129
|
sections.push(`✔ discovery complete (${inspected})`);
|
|
112
130
|
} else {
|
|
113
|
-
|
|
131
|
+
// The headline states the incompleteness and its consequence; the clauses
|
|
132
|
+
// below state WHY, one per failed coverage axis. Blaming the whole-file
|
|
133
|
+
// count in the headline alone would read "0 files could not be analyzed"
|
|
134
|
+
// over a zero-analysis run (#619) — incomplete, with a reason of nothing.
|
|
114
135
|
sections.push(
|
|
115
|
-
`✖ discovery incomplete —
|
|
116
|
-
`could not be analyzed, so these observations may under-represent the workspace (${inspected})`,
|
|
136
|
+
`✖ discovery incomplete — these observations may under-represent the workspace (${inspected})`,
|
|
117
137
|
);
|
|
138
|
+
const clauses = formatCoverageIncomplete(coverageIncomplete ?? []);
|
|
139
|
+
if (clauses !== "") sections.push(clauses);
|
|
118
140
|
}
|
|
119
141
|
|
|
120
142
|
const projectWord = discovery.projects.length === 1 ? "project" : "projects";
|
|
@@ -12,12 +12,21 @@
|
|
|
12
12
|
* The coverage claim sits ABOVE the listing, not below it, so the reader knows
|
|
13
13
|
* whether the snapshot is complete before reading any entry — an incomplete
|
|
14
14
|
* snapshot printed in full would have the "this may under-represent" warning
|
|
15
|
-
* buried at the bottom.
|
|
15
|
+
* buried at the bottom. Under an incomplete claim sit the reason clauses the
|
|
16
|
+
* run withheld the verdict over, worded by `../verdict.mjs`'s
|
|
17
|
+
* `coverageIncompleteReasons` and rendered through `./text.mjs`'s
|
|
18
|
+
* `formatCoverageIncomplete` — the same clauses, in the same `⚠` rendering,
|
|
19
|
+
* `check`'s text report prints, so a terminal reader is told why the verdict
|
|
20
|
+
* is withheld whichever face ran. A zero-analysis run is the case that needs
|
|
21
|
+
* this: its `notAnalyzed` list is empty, so a count-bearing headline would
|
|
22
|
+
* blame zero failures for an incomplete snapshot.
|
|
16
23
|
*
|
|
17
24
|
* This module decides nothing. A formatter that filtered would be a rule
|
|
18
25
|
* wearing a formatter's name (`../README.md`).
|
|
19
26
|
*/
|
|
20
27
|
|
|
28
|
+
import { formatCoverageIncomplete } from "./text.mjs";
|
|
29
|
+
|
|
21
30
|
/**
|
|
22
31
|
* One project as a line: name, root, type, and tags.
|
|
23
32
|
*
|
|
@@ -44,7 +53,13 @@ function formatEdge(edge) {
|
|
|
44
53
|
* The whole graph report.
|
|
45
54
|
*
|
|
46
55
|
* @param {{projects: object[], dependencies: object[], workspaceLayout: object,
|
|
47
|
-
* workspaceLayoutSource: string, coverage: object
|
|
56
|
+
* workspaceLayoutSource: string, coverage: object,
|
|
57
|
+
* coverageIncomplete?: string[]}} input
|
|
58
|
+
* `coverageIncomplete` is the withheld-verdict clause list
|
|
59
|
+
* (`../verdict.mjs`'s `coverageIncompleteReasons`, handed through
|
|
60
|
+
* `../../commands/graph.mjs`) — rendered below the incomplete headline,
|
|
61
|
+
* empty exactly when the snapshot is complete, and optional because a
|
|
62
|
+
* complete snapshot carries no clauses to render.
|
|
48
63
|
* @returns {string}
|
|
49
64
|
*/
|
|
50
65
|
export function formatGraphReport({
|
|
@@ -53,6 +68,7 @@ export function formatGraphReport({
|
|
|
53
68
|
workspaceLayout,
|
|
54
69
|
workspaceLayoutSource,
|
|
55
70
|
coverage,
|
|
71
|
+
coverageIncomplete,
|
|
56
72
|
}) {
|
|
57
73
|
const sections = [];
|
|
58
74
|
|
|
@@ -66,11 +82,15 @@ export function formatGraphReport({
|
|
|
66
82
|
if (coverage.complete) {
|
|
67
83
|
sections.push(`✔ graph snapshot complete (${inspected})`);
|
|
68
84
|
} else {
|
|
69
|
-
|
|
85
|
+
// The headline states the incompleteness and its consequence; the clauses
|
|
86
|
+
// below state WHY, one per failed coverage axis. Blaming the whole-file
|
|
87
|
+
// count in the headline alone would read "0 files could not be analyzed"
|
|
88
|
+
// over a zero-analysis run (#612) — incomplete, with a reason of nothing.
|
|
70
89
|
sections.push(
|
|
71
|
-
`✖ graph snapshot incomplete —
|
|
72
|
-
`could not be analyzed, so this snapshot may under-represent the architecture (${inspected})`,
|
|
90
|
+
`✖ graph snapshot incomplete — this snapshot may under-represent the architecture (${inspected})`,
|
|
73
91
|
);
|
|
92
|
+
const clauses = formatCoverageIncomplete(coverageIncomplete ?? []);
|
|
93
|
+
if (clauses !== "") sections.push(clauses);
|
|
74
94
|
}
|
|
75
95
|
|
|
76
96
|
// Layout line
|