@ecoma-io/archkeep 0.20.1 → 0.22.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 (41) hide show
  1. package/cli.mjs +156 -66
  2. package/package.json +1 -1
  3. package/src/analysis/contract.md +32 -5
  4. package/src/analysis/source-util.mjs +107 -0
  5. package/src/analysis/typescript.mjs +86 -5
  6. package/src/commands/change.mjs +59 -28
  7. package/src/commands/check.mjs +65 -26
  8. package/src/commands/completeness.mjs +708 -0
  9. package/src/commands/context-command.mjs +13 -5
  10. package/src/commands/context.mjs +31 -4
  11. package/src/commands/coverage-verdict.mjs +184 -0
  12. package/src/commands/debt.mjs +18 -15
  13. package/src/commands/delta-classify.mjs +13 -18
  14. package/src/commands/delta.mjs +95 -33
  15. package/src/commands/diff.mjs +31 -24
  16. package/src/commands/discover.mjs +30 -10
  17. package/src/commands/drift.mjs +21 -21
  18. package/src/commands/edge-constraints.mjs +47 -1
  19. package/src/commands/evaluation-primitives.mjs +691 -0
  20. package/src/commands/evolution.mjs +27 -10
  21. package/src/commands/explain.mjs +14 -13
  22. package/src/commands/fitness.mjs +20 -19
  23. package/src/commands/graph.mjs +14 -5
  24. package/src/commands/health.mjs +12 -5
  25. package/src/commands/history.mjs +29 -15
  26. package/src/commands/impact-statement.mjs +31 -409
  27. package/src/commands/impact.mjs +18 -18
  28. package/src/commands/plan-context-command.mjs +10 -5
  29. package/src/commands/provenance-command.mjs +33 -2
  30. package/src/commands/reconcile.mjs +14 -17
  31. package/src/commands/scenario-evaluation.mjs +363 -198
  32. package/src/commands/scenario.mjs +32 -21
  33. package/src/commands/waivers.mjs +36 -28
  34. package/src/governance/evolution-event.mjs +62 -9
  35. package/src/governance/provenance-graph.mjs +479 -0
  36. package/src/intent/intent-manifest.json +83 -39
  37. package/src/report/json.mjs +32 -5
  38. package/src/report/provenance-text.mjs +30 -7
  39. package/src/report/text.mjs +82 -12
  40. package/src/verdict.mjs +78 -36
  41. package/src/workspace.mjs +126 -2
@@ -43,9 +43,10 @@
43
43
  * comparison (never `localeCompare`), so two runs over an unchanged tree and
44
44
  * intent produce byte-identical text and JSON.
45
45
  */
46
- import { isWholeFileFailure } from "../analysis/source-util.mjs";
46
+ import { blindSpotRows } from "../analysis/source-util.mjs";
47
47
  import { jsonEnvelope, renderJson } from "../report/json.mjs";
48
48
  import { resolveProvenance } from "./provenance.mjs";
49
+ import { coverageRefusal, coverageVerdict } from "./coverage-verdict.mjs";
49
50
  import { judgeIntent } from "../architecture-intent/judge.mjs";
50
51
  import { computeIntentFingerprint } from "../architecture-intent/intent-fingerprint.mjs";
51
52
  import { INTENT_FILE, loadIntent } from "../architecture-intent/model.mjs";
@@ -84,9 +85,12 @@ function intentRows(intent) {
84
85
  * @param {{loadIntentOverride?: (root: string) => Promise<object>}} [io]
85
86
  * Injectable intent loader for tests.
86
87
  * @param {{propose?: boolean}} [options] `--propose` adds the ranked candidate list.
87
- * @returns {Promise<{status: "ok", reconcile: object, coverage: object,
88
+ * @returns {Promise<{status: "ok"|"no-verdict", reconcile?: object, coverage: object,
88
89
  * report: {text: string, json: string}}>}
89
- * @throws {Error} on every condition the header lists, all exit-3 class.
90
+ * `status: "no-verdict"` carries no `reconcile` payload the verdict was
91
+ * withheld, and the envelope's `coverage` block is the whole answer (#608).
92
+ * @throws {Error} on every condition the header lists except the coverage one,
93
+ * which returns instead of throwing.
90
94
  */
91
95
  export async function reconcileCommand(commandContext, io = {}, options = {}) {
92
96
  const { root, provider, marker, analysis } = commandContext;
@@ -94,17 +98,12 @@ export async function reconcileCommand(commandContext, io = {}, options = {}) {
94
98
  refuseIncompleteGraph(commandContext);
95
99
 
96
100
  // A reconcile verdict cannot be established over a tree it could not fully
97
- // read — the same fail-closed condition `drift` enforces.
98
- const notAnalyzed = analysis.failures
99
- .filter(isWholeFileFailure)
100
- .map(({ sourceFile, reason }) => ({ file: sourceFile, reason }));
101
-
102
- if (notAnalyzed.length > 0) {
103
- throw new Error(
104
- `archkeep: reconcile has incomplete coverage — ${notAnalyzed.length} file` +
105
- `${notAnalyzed.length === 1 ? "" : "s"} could not be analyzed, so every "absent" score ` +
106
- `would be ambiguous between "gone" and "never seen". Fix the unanalyzed files and re-run.`,
107
- );
101
+ // read — the same fail-closed condition `drift` enforces, refused through
102
+ // the same structured envelope `./coverage-verdict.mjs` builds (#608): the
103
+ // verdict is withheld in-band, where a parser and `--output` can read it.
104
+ const completeness = coverageVerdict(commandContext);
105
+ if (!completeness.complete) {
106
+ return coverageRefusal({ command: "reconcile", commandContext, what: "reconciling" });
108
107
  }
109
108
 
110
109
  const intent = await (io.loadIntentOverride ?? loadIntent)(root, {
@@ -146,9 +145,7 @@ export async function reconcileCommand(commandContext, io = {}, options = {}) {
146
145
  // Reconcile reads only the graph — provider failures are the same blind
147
146
  // spots every other command reports, and a blind spot never prevents a
148
147
  // verdict.
149
- blindSpots: analysis.failures
150
- .filter((failure) => !isWholeFileFailure(failure))
151
- .map(({ sourceFile, line, column, reason }) => ({ file: sourceFile, line, column, reason })),
148
+ blindSpots: blindSpotRows(analysis.failures),
152
149
  // Coverage notes (e.g. an `optional: true` allowed row the team has not
153
150
  // built yet) ride here so "optional and absent" never reads as "never
154
151
  // checked".