@ecoma-io/archkeep 0.24.1 → 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 +315 -55
- 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-snapshot.mjs +25 -4
- package/src/commands/delta.mjs +301 -67
- package/src/commands/diff.mjs +48 -9
- 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 +119 -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/snapshot-meta.mjs +33 -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/custom-rules/values.mjs +1 -4
- 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/fitness-registry.mjs +14 -1
- package/src/governance/provenance-record.mjs +0 -152
- package/src/governance/verdict.mjs +62 -7
- package/src/intent/intent-manifest.json +22 -16
- 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 +7 -4
- 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/values.mjs +11 -0
- package/src/verdict.mjs +127 -16
|
@@ -82,6 +82,7 @@ import {
|
|
|
82
82
|
classifyEvolution,
|
|
83
83
|
edgeEvolutionIdentity,
|
|
84
84
|
eventDedupeKey,
|
|
85
|
+
EVENT_DISPOSITIONS,
|
|
85
86
|
eventId,
|
|
86
87
|
EVOLUTION_EVENT_SCHEMA_VERSION,
|
|
87
88
|
} from "../governance/evolution-event.mjs";
|
|
@@ -838,14 +839,33 @@ function buildTransitionEvent(from, to, transition, comparison) {
|
|
|
838
839
|
* axis at any transition is surfaced as `{available: false, reason}` naming
|
|
839
840
|
* the transition index, never folded into a fabricated clean aggregate.
|
|
840
841
|
*
|
|
842
|
+
* The disposition loop latches the event vocabulary: a comparison whose
|
|
843
|
+
* disposition is unknown or absent THROWS naming it — the `?? 1` it replaced
|
|
844
|
+
* defaulted a stranger to rank 1, accepted's own rank, so a garbled
|
|
845
|
+
* disposition could never be the summary's worst. Production dispositions
|
|
846
|
+
* arrive validated (`classifyEvolution`, `readEvents`), so the throw fires
|
|
847
|
+
* only where archkeep itself is buggy — the same latch `deltaDisposition`
|
|
848
|
+
* holds.
|
|
849
|
+
*
|
|
841
850
|
* @param {object[]} comparisons The per-transition comparison objects.
|
|
842
851
|
* @returns {object} The summary.
|
|
852
|
+
* @throws {Error} On a comparison whose `disposition` is outside
|
|
853
|
+
* `EVENT_DISPOSITIONS`, naming the value.
|
|
843
854
|
*/
|
|
844
|
-
function buildEvolutionSummary(comparisons) {
|
|
855
|
+
export function buildEvolutionSummary(comparisons) {
|
|
845
856
|
const dispositionRank = { accepted: 1, rejected: 2, "no-verdict": 3 };
|
|
846
857
|
let disposition = "accepted";
|
|
847
858
|
for (const comparison of comparisons) {
|
|
848
|
-
if ((
|
|
859
|
+
if (!EVENT_DISPOSITIONS.includes(comparison.disposition)) {
|
|
860
|
+
throw new Error(
|
|
861
|
+
`the evolution summary folds dispositions by rank, and ${JSON.stringify(
|
|
862
|
+
comparison.disposition,
|
|
863
|
+
)} is not one of [${EVENT_DISPOSITIONS.join(", ")}] — an unknown disposition would ` +
|
|
864
|
+
`default to rank 1, accepted's own rank, and could never be the summary's worst. ` +
|
|
865
|
+
`This is a bug in archkeep, not a fact about the workspace.`,
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (dispositionRank[comparison.disposition] > dispositionRank[disposition]) {
|
|
849
869
|
disposition = comparison.disposition;
|
|
850
870
|
}
|
|
851
871
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `explain` capability facade — the word's verb roster as explicit named
|
|
3
|
+
* re-exports: `explain`.
|
|
4
|
+
*
|
|
5
|
+
* Pure surface module: no judgment, no defaults, no logic — every export is
|
|
6
|
+
* a re-export of a command module. `cli.mjs` routes its verb imports through
|
|
7
|
+
* the facade so the vocabulary's code referent is visible to the import
|
|
8
|
+
* graph, not just to a comment (PD-18,
|
|
9
|
+
* ../../../../docs/architecture/refactor/DECISIONS.md).
|
|
10
|
+
*/
|
|
11
|
+
export { explain } from "./explain.mjs";
|
package/src/commands/explain.mjs
CHANGED
|
@@ -86,8 +86,10 @@ import { findConstraintsFor } from "../rules/tags.mjs";
|
|
|
86
86
|
import { findProjectForPath, createProjectRootMappings } from "../rules/specifiers.mjs";
|
|
87
87
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
88
88
|
import { coverageVerdict } from "./coverage-verdict.mjs";
|
|
89
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
89
90
|
import { formatExplainReport } from "../report/explain-text.mjs";
|
|
90
91
|
import { resolveProvenance } from "./provenance.mjs";
|
|
92
|
+
import { resolvePolicy } from "./policy.mjs";
|
|
91
93
|
import { readAdrContext } from "./adr.mjs";
|
|
92
94
|
import { lineage } from "../governance/decision-graph.mjs";
|
|
93
95
|
import { unresolvedDecisionRefNote } from "./provenance-command.mjs";
|
|
@@ -613,3 +615,25 @@ export function explainCommand(site, commandContext, config, options = {}) {
|
|
|
613
615
|
},
|
|
614
616
|
};
|
|
615
617
|
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* `explain` as the CLI drives it: the shared preamble — command context,
|
|
621
|
+
* then the boundary law — resolved here so `../../cli.mjs`'s driver only
|
|
622
|
+
* wires options, IO seams, and where output lands (`./README.md`). The
|
|
623
|
+
* engine this returns from is `explainCommand` above, unchanged.
|
|
624
|
+
*
|
|
625
|
+
* @param {{config: string|null, paths: string[]}} options This run's parsed
|
|
626
|
+
* flags; `paths[0]` is the site, a `file:line:column` string.
|
|
627
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
628
|
+
* seams a test injects, the same ones `check` takes.
|
|
629
|
+
* @returns {Promise<object>} `explainCommand`'s result, unmodified.
|
|
630
|
+
*/
|
|
631
|
+
export async function explain(options, { cwd, readGraph, listFiles }) {
|
|
632
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
633
|
+
// The config's location is a separate fact from the workspace root.
|
|
634
|
+
// Same loading logic as `check` (`resolvePolicy`) — a `--config`
|
|
635
|
+
// overrides the workspace's own `boundaryConfig`, profile-aware the same
|
|
636
|
+
// way `check` is.
|
|
637
|
+
const { config } = await resolvePolicy(options, commandContext, cwd);
|
|
638
|
+
return explainCommand(options.paths[0], commandContext, config);
|
|
639
|
+
}
|
package/src/commands/fitness.mjs
CHANGED
|
@@ -49,13 +49,18 @@ import { blindSpotRows } from "../analysis/source-util.mjs";
|
|
|
49
49
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
50
50
|
import { formatFitnessSection } from "../report/text.mjs";
|
|
51
51
|
import { resolveProvenance } from "./provenance.mjs";
|
|
52
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
52
53
|
import { coverageRefusal, coverageVerdict } from "./coverage-verdict.mjs";
|
|
53
54
|
import { driftForCheck } from "./drift.mjs";
|
|
55
|
+
import { resolvePolicy } from "./policy.mjs";
|
|
54
56
|
import {
|
|
55
57
|
evaluateFitness,
|
|
56
58
|
fitnessSnapshot,
|
|
57
59
|
fitnessVerdictFor,
|
|
58
60
|
} from "../governance/fitness-registry.mjs";
|
|
61
|
+
import { VERDICTS, isVerdict } from "../governance/verdict.mjs";
|
|
62
|
+
import { buildDecision } from "../report/evidence.mjs";
|
|
63
|
+
import { describe } from "../values.mjs";
|
|
59
64
|
|
|
60
65
|
/**
|
|
61
66
|
* The `fitness` command's own text report: the same verdict table `check`'s
|
|
@@ -104,6 +109,55 @@ export function declaresFitness(config) {
|
|
|
104
109
|
return config !== null && config !== undefined && config.fitness !== undefined;
|
|
105
110
|
}
|
|
106
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The `fitness` exit fold, lifted out of `fitnessCommand` as a pure function
|
|
114
|
+
* so the mapping from the overall verdict to the status is a fact a test can
|
|
115
|
+
* pin (`./change.mjs`'s `reconcileDisposition` is the arrangement's
|
|
116
|
+
* precedent). D-09, as below: `fail` → `findings`/1, `unknown` →
|
|
117
|
+
* `no-verdict`/3, `pass` and an all-`not_applicable` run alike → `ok`/0.
|
|
118
|
+
*
|
|
119
|
+
* The input latch runs first: `overall.verdict` is the handshake with
|
|
120
|
+
* `fitnessVerdictFor`, and a value the fold does not recognize matches
|
|
121
|
+
* neither the `fail` nor the `unknown` arm — it would fall to `ok`, a clean
|
|
122
|
+
* exit over a run the fold could not read. A verdict outside the four states
|
|
123
|
+
* is refused as a no-verdict whose single reason names it (`refused`), never
|
|
124
|
+
* folded past. The literals stay hand-rolled per site by decision — the
|
|
125
|
+
* carrier folds are the pinned baseline INV-2 names, not a table to be
|
|
126
|
+
* converged (`docs/architecture/refactor/AUTHORITY-MAP.md`).
|
|
127
|
+
*
|
|
128
|
+
* `overall.decisions` is validated for the same reason the delta fold
|
|
129
|
+
* validates buckets the fold itself does not count first: `result.functions`
|
|
130
|
+
* and the text table render them downstream of this fold, so a malformed one
|
|
131
|
+
* is refused while there is still a verdict to withhold.
|
|
132
|
+
*
|
|
133
|
+
* @param {{verdict: string, decisions: object[]}} overall From
|
|
134
|
+
* `fitnessVerdictFor`.
|
|
135
|
+
* @returns {{status: "ok"|"findings"|"no-verdict", exitCode: 0|1|3,
|
|
136
|
+
* refused?: undefined}|
|
|
137
|
+
* {status: "no-verdict", exitCode: 3, refused: string}}
|
|
138
|
+
*/
|
|
139
|
+
export function fitnessFold(overall) {
|
|
140
|
+
if (!isVerdict(overall?.verdict)) {
|
|
141
|
+
return {
|
|
142
|
+
status: "no-verdict",
|
|
143
|
+
exitCode: 3,
|
|
144
|
+
refused: `the fitness fold maps "overall.verdict", which is ${describe(overall?.verdict)} — a verdict outside ${VERDICTS.join(", ")} matches neither the fail nor the unknown lane, and reads "ok". This is a bug in archkeep, not a fact about the workspace.`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (!Array.isArray(overall?.decisions)) {
|
|
148
|
+
return {
|
|
149
|
+
status: "no-verdict",
|
|
150
|
+
exitCode: 3,
|
|
151
|
+
refused: `the fitness fold reads "overall.decisions", which is ${describe(overall?.decisions)} — the verdict table the run reports is built from it below, and a list that is not an array renders as an empty table. This is a bug in archkeep, not a fact about the workspace.`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return overall.verdict === "fail"
|
|
155
|
+
? { status: "findings", exitCode: 1 }
|
|
156
|
+
: overall.verdict === "unknown"
|
|
157
|
+
? { status: "no-verdict", exitCode: 3 }
|
|
158
|
+
: { status: "ok", exitCode: 0 };
|
|
159
|
+
}
|
|
160
|
+
|
|
107
161
|
/**
|
|
108
162
|
* Runs the `fitness` command: loads the boundary policy, evaluates every
|
|
109
163
|
* declared function against the workspace's facts, and renders the verdict
|
|
@@ -115,8 +169,8 @@ export function declaresFitness(config) {
|
|
|
115
169
|
*
|
|
116
170
|
* @param {object} commandContext From `resolveCommandContext`.
|
|
117
171
|
* @param {{config?: object|null}} [io] The loaded policy, injectable for tests.
|
|
118
|
-
* @returns {Promise<{status: "ok"|"findings"|"no-verdict",
|
|
119
|
-
* coverage: object, report: {text: string, json: string}}>}
|
|
172
|
+
* @returns {Promise<{status: "ok"|"findings"|"no-verdict", exitCode: 0|1|3,
|
|
173
|
+
* fitness?: object, coverage: object, report: {text: string, json: string}}>}
|
|
120
174
|
* `status: "no-verdict"` from the coverage refusal carries no `fitness`
|
|
121
175
|
* payload — the verdict was withheld, and the envelope's `coverage` block is
|
|
122
176
|
* the whole answer (#608).
|
|
@@ -207,13 +261,40 @@ export async function fitnessCommand(commandContext, io = {}) {
|
|
|
207
261
|
// every function is `not_applicable` are both `ok`: nothing failed and
|
|
208
262
|
// nothing stayed undetermined. The status↔exitCode pair is asserted by
|
|
209
263
|
// `jsonEnvelope` (3-on-no-verdict), so a wrong mapping here cannot ship.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
264
|
+
// `fitnessFold` above owns the mapping and its input latch; a refusal
|
|
265
|
+
// withholds the verdict table exactly the coverage refusal does, and the
|
|
266
|
+
// decision it carries names the input the fold could not read.
|
|
267
|
+
const fold = fitnessFold(overall);
|
|
268
|
+
if (fold.refused !== undefined) {
|
|
269
|
+
return {
|
|
270
|
+
status: fold.status,
|
|
271
|
+
exitCode: fold.exitCode,
|
|
272
|
+
coverage: { ...coverage, notes: [...coverage.notes, fold.refused] },
|
|
273
|
+
report: {
|
|
274
|
+
text: `fitness: no verdict — ${fold.refused}\n`,
|
|
275
|
+
json: renderJson(
|
|
276
|
+
jsonEnvelope({
|
|
277
|
+
command: "fitness",
|
|
278
|
+
context,
|
|
279
|
+
status: fold.status,
|
|
280
|
+
exitCode: fold.exitCode,
|
|
281
|
+
coverage: { ...coverage, notes: [...coverage.notes, fold.refused] },
|
|
282
|
+
// The refusal-withheld payload, stated the way `coverageRefusal`
|
|
283
|
+
// states it: `jsonEnvelope` requires the key, the refusal has no
|
|
284
|
+
// result to report.
|
|
285
|
+
result: undefined,
|
|
286
|
+
decision: buildDecision({
|
|
287
|
+
status: fold.status,
|
|
288
|
+
coverageComplete: true,
|
|
289
|
+
findings: 0,
|
|
290
|
+
reason: fold.refused,
|
|
291
|
+
}),
|
|
292
|
+
}),
|
|
293
|
+
),
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const { status, exitCode } = fold;
|
|
217
298
|
const result = { verdict: overall.verdict, functions: decisions };
|
|
218
299
|
|
|
219
300
|
const report = {
|
|
@@ -223,5 +304,33 @@ export async function fitnessCommand(commandContext, io = {}) {
|
|
|
223
304
|
),
|
|
224
305
|
};
|
|
225
306
|
|
|
226
|
-
return { status, fitness: result, coverage, report };
|
|
307
|
+
return { status, exitCode, fitness: result, coverage, report };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* `fitness` as the CLI drives it: the shared preamble — command context,
|
|
312
|
+
* then the boundary law — resolved here so `../../cli.mjs`'s driver only
|
|
313
|
+
* wires options, IO seams, and where output lands (`./README.md`). The
|
|
314
|
+
* engine this returns from is `fitnessCommand` above, unchanged.
|
|
315
|
+
*
|
|
316
|
+
* @param {{config: string|null, paths: string[]}} options This run's parsed
|
|
317
|
+
* flags.
|
|
318
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
319
|
+
* seams a test injects, the same ones `check` takes.
|
|
320
|
+
* @returns {Promise<object>} `fitnessCommand`'s result, unmodified.
|
|
321
|
+
*/
|
|
322
|
+
export async function fitness(options, { cwd, readGraph, listFiles }) {
|
|
323
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
324
|
+
// Fitness is part of the run's boundary law, so the law is loaded the same
|
|
325
|
+
// way `check` loads it (`resolvePolicy`) and `--config` wins the same
|
|
326
|
+
// way — resolved against the working directory, never against this
|
|
327
|
+
// tool's own location, profile-aware the same way `check` is. A malformed
|
|
328
|
+
// law throws here, exit 3, exactly as in `check`. A profile's `block` may
|
|
329
|
+
// carry a `fitness` key (`docs/concepts/profiles.md` names four block
|
|
330
|
+
// keys, fitness among them), so a profile-selected workspace folds the
|
|
331
|
+
// declared functions the same way a file-selected one does — a profile
|
|
332
|
+
// that declares none reaches `fitnessCommand`'s own "declares no fitness
|
|
333
|
+
// functions" refusal rather than a config-loading failure.
|
|
334
|
+
const { config } = await resolvePolicy(options, commandContext, cwd);
|
|
335
|
+
return fitnessCommand(commandContext, { config });
|
|
227
336
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `govern` capability facade — the word's verb roster as explicit named
|
|
3
|
+
* re-exports: `waivers`, `report`, `debt`, `provenance`, `decisions`, `adr`.
|
|
4
|
+
*
|
|
5
|
+
* Pure surface module: no judgment, no defaults, no logic — every export is
|
|
6
|
+
* a re-export of a command module. `cli.mjs` routes its verb imports through
|
|
7
|
+
* the facade so the vocabulary's code referent is visible to the import
|
|
8
|
+
* graph, not just to a comment (PD-18,
|
|
9
|
+
* ../../../../docs/architecture/refactor/DECISIONS.md).
|
|
10
|
+
*/
|
|
11
|
+
export { waivers } from "./waivers.mjs";
|
|
12
|
+
export { report } from "./report.mjs";
|
|
13
|
+
export { debt } from "./debt.mjs";
|
|
14
|
+
export { provenanceCommand } from "./provenance-command.mjs";
|
|
15
|
+
export { decisions } from "./decisions.mjs";
|
|
16
|
+
export { adrCommand } from "./adr.mjs";
|
package/src/commands/graph.mjs
CHANGED
|
@@ -41,7 +41,9 @@ import { DEFAULT_WORKSPACE_LAYOUT } from "../rules/specifiers.mjs";
|
|
|
41
41
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
42
42
|
import { formatGraphReport } from "../report/graph-text.mjs";
|
|
43
43
|
import { coverageIncompleteReasons } from "../verdict.mjs";
|
|
44
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
44
45
|
import { coverageVerdict } from "./coverage-verdict.mjs";
|
|
46
|
+
import { resolveDescribedPolicy } from "./policy.mjs";
|
|
45
47
|
import { resolveProvenance } from "./provenance.mjs";
|
|
46
48
|
|
|
47
49
|
/**
|
|
@@ -342,3 +344,40 @@ export function graphCommand(commandContext, { config = null } = {}) {
|
|
|
342
344
|
},
|
|
343
345
|
};
|
|
344
346
|
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* `graph` as the CLI drives it: the shared preamble — command context, then
|
|
350
|
+
* the workspace's described policy — resolved here, so `../../cli.mjs`'s
|
|
351
|
+
* driver only wires options, IO seams, and where output lands
|
|
352
|
+
* (`./README.md`). The engine this returns from is `graphCommand` above,
|
|
353
|
+
* unchanged.
|
|
354
|
+
*
|
|
355
|
+
* The policy is DESCRIBED, not judged: `graph` describes the project graph,
|
|
356
|
+
* not the boundary law — it reads no constraint row and judges nothing
|
|
357
|
+
* against one — so a workspace that has not written a law yet must not be
|
|
358
|
+
* refused here. Every arm of that decision — what is skipped is the load of
|
|
359
|
+
* a file that is NOT THERE, the `boundaryConfigDeclared` bit that keeps the
|
|
360
|
+
* guard to the un-overridden default, and why a law someone named and then
|
|
361
|
+
* deleted stays loud — lives in `resolveDescribedPolicy`
|
|
362
|
+
* (`./policy.mjs`) rather than here, so the descriptive commands and the
|
|
363
|
+
* MCP face that serves them cannot disagree about what "no law declared"
|
|
364
|
+
* means.
|
|
365
|
+
*
|
|
366
|
+
* @param {{config: string|null, paths: string[]}} options This run's parsed
|
|
367
|
+
* flags — `graph` has no `--config` flag, so `config` is always `null`
|
|
368
|
+
* and the workspace's own default is what resolves.
|
|
369
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
370
|
+
* seams a test injects, the same ones `check` takes.
|
|
371
|
+
* @returns {Promise<object>} `graphCommand`'s result, unmodified.
|
|
372
|
+
*/
|
|
373
|
+
export async function graph(options, { cwd, readGraph, listFiles }) {
|
|
374
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
375
|
+
// The fingerprint is why the config loads even though `graph` judges
|
|
376
|
+
// nothing: `diff` warns when the policy changed between runs, and a
|
|
377
|
+
// profile-selected workspace's `boundaryConfig` names a profile rather
|
|
378
|
+
// than a file, resolved the same way `check` resolves it
|
|
379
|
+
// (`resolveDescribedPolicy`), so the fingerprint moves with a profile edit
|
|
380
|
+
// the same way it already does with a file or inline-object edit.
|
|
381
|
+
const { config } = await resolveDescribedPolicy(options, commandContext, cwd);
|
|
382
|
+
return graphCommand(commandContext, { config });
|
|
383
|
+
}
|
package/src/commands/health.mjs
CHANGED
|
@@ -49,10 +49,12 @@ import {
|
|
|
49
49
|
unresolvableLiteralCount,
|
|
50
50
|
} from "../analysis/source-util.mjs";
|
|
51
51
|
import { buildDependencies, buildProjects } from "./graph.mjs";
|
|
52
|
+
import { isAbsolute, resolve } from "node:path";
|
|
52
53
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
53
54
|
import { formatHealthReport } from "../report/health-text.mjs";
|
|
54
55
|
import { resolveProvenance } from "./provenance.mjs";
|
|
55
56
|
import { readSnapshots } from "./history.mjs";
|
|
57
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
56
58
|
import {
|
|
57
59
|
boundaryMetrics,
|
|
58
60
|
couplingMetrics,
|
|
@@ -61,6 +63,8 @@ import {
|
|
|
61
63
|
structuralMetrics,
|
|
62
64
|
} from "../governance/metrics.mjs";
|
|
63
65
|
import { judgeIntent } from "../architecture-intent/judge.mjs";
|
|
66
|
+
import { loadIntentIfTracked } from "../architecture-intent/model.mjs";
|
|
67
|
+
import { resolvePolicy } from "./policy.mjs";
|
|
64
68
|
|
|
65
69
|
/**
|
|
66
70
|
* Computes the intent verdict the fitness metric reads — the same `judgeIntent`
|
|
@@ -222,3 +226,33 @@ export function healthCommand(commandContext, io = {}) {
|
|
|
222
226
|
},
|
|
223
227
|
};
|
|
224
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `health` as the CLI drives it: the shared preamble — command context, the
|
|
232
|
+
* boundary law, the tracked intent — resolved here, so `../../cli.mjs`'s
|
|
233
|
+
* driver only wires options, IO seams, and where output lands
|
|
234
|
+
* (`./README.md`). The engine this returns from is `healthCommand` above,
|
|
235
|
+
* unchanged.
|
|
236
|
+
*
|
|
237
|
+
* @param {{config: string|null, paths: string[]}} options This run's parsed
|
|
238
|
+
* flags; at most one positional, the snapshot directory for trends.
|
|
239
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
240
|
+
* seams a test injects, the same ones `check` takes.
|
|
241
|
+
* @returns {Promise<object>} `healthCommand`'s result, unmodified.
|
|
242
|
+
*/
|
|
243
|
+
export async function health(options, { cwd, readGraph, listFiles }) {
|
|
244
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
245
|
+
// The boundary law and the intent, the same loading every command does
|
|
246
|
+
// (`resolvePolicy`) — a `--config` overrides the workspace's own
|
|
247
|
+
// `boundaryConfig`, profile-aware the same way `check` is, and the
|
|
248
|
+
// intent is the tracked root `architecture-intent.json` (or absent).
|
|
249
|
+
const { config } = await resolvePolicy(options, commandContext, cwd);
|
|
250
|
+
const intent = await loadIntentIfTracked(commandContext.root, commandContext.tracked);
|
|
251
|
+
const trendDir =
|
|
252
|
+
options.paths.length === 1
|
|
253
|
+
? isAbsolute(options.paths[0])
|
|
254
|
+
? options.paths[0]
|
|
255
|
+
: resolve(cwd, options.paths[0])
|
|
256
|
+
: null;
|
|
257
|
+
return healthCommand(commandContext, { config, intent, trendDir });
|
|
258
|
+
}
|
package/src/commands/history.mjs
CHANGED
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
*/
|
|
73
73
|
import { createHash } from "node:crypto";
|
|
74
74
|
import { existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
75
|
-
import { basename, join, resolve } from "node:path";
|
|
75
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
76
76
|
|
|
77
77
|
import {
|
|
78
78
|
blindSpotRows,
|
|
@@ -85,9 +85,11 @@ import { classifyEvolution } from "../governance/evolution-event.mjs";
|
|
|
85
85
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
86
86
|
import { formatHistoryReport } from "../report/history-text.mjs";
|
|
87
87
|
import { computeDiff, parseBaseline } from "./diff.mjs";
|
|
88
|
-
import { buildDependencies, buildProjects } from "./graph.mjs";
|
|
88
|
+
import { buildDependencies, buildProjects, computePolicyFingerprint } from "./graph.mjs";
|
|
89
89
|
import { resolveProvenance } from "./provenance.mjs";
|
|
90
90
|
import { compareSnapshotMetadata } from "./snapshot-meta.mjs";
|
|
91
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
92
|
+
import { resolvePolicy } from "./policy.mjs";
|
|
91
93
|
|
|
92
94
|
/**
|
|
93
95
|
* A graph envelope's architecture identity: the part that determines whether
|
|
@@ -112,6 +114,12 @@ import { compareSnapshotMetadata } from "./snapshot-meta.mjs";
|
|
|
112
114
|
* Provider and provenance changes surface through the transition
|
|
113
115
|
* classification instead.
|
|
114
116
|
*
|
|
117
|
+
* This function is the graph-snapshot family's ONE identity — evolution event
|
|
118
|
+
* sides consume its output through `eventSnapshotSide` below, never a second
|
|
119
|
+
* derivation (`../../../../docs/adr/0008-snapshot-identity-per-family.md`
|
|
120
|
+
* records the one-identity-per-family decision, including why the evidence
|
|
121
|
+
* snapshot family deliberately carries no id at all).
|
|
122
|
+
*
|
|
115
123
|
* @param {{projects: object[], dependencies: object[], policy?: {fingerprint: string}|null}} snapshot
|
|
116
124
|
* @returns {string} A hex-encoded SHA-256.
|
|
117
125
|
*/
|
|
@@ -136,6 +144,34 @@ export function snapshotIdentity({ projects, dependencies, policy }) {
|
|
|
136
144
|
return createHash("sha256").update(canonical).digest("hex");
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
/**
|
|
148
|
+
* An evolution event's identity side: the reference to ONE state — a revision
|
|
149
|
+
* when one is known, plus the snapshot identity of the graph that side was
|
|
150
|
+
* judged over. `delta` and `change` build every side through this one
|
|
151
|
+
* function so the identity spelling cannot drift between them, and a storage
|
|
152
|
+
* path can never re-enter the tuple a committed event store dedupes on
|
|
153
|
+
* (`docs/concepts/evolution.md`'s "Nor does a storage path"). `history` and
|
|
154
|
+
* `evolution` spell the same `{revision, snapshot}` shape directly, from a
|
|
155
|
+
* snapshot whose identity is already computed.
|
|
156
|
+
*
|
|
157
|
+
* @param {{revision?: string|null, projects: object[], dependencies: object[],
|
|
158
|
+
* policyFingerprint?: string|null}} side
|
|
159
|
+
* @returns {{revision?: string, snapshot: string}} The identity side.
|
|
160
|
+
*/
|
|
161
|
+
export function eventSnapshotSide({ revision, projects, dependencies, policyFingerprint }) {
|
|
162
|
+
return {
|
|
163
|
+
...(typeof revision === "string" ? { revision } : {}),
|
|
164
|
+
snapshot: snapshotIdentity({
|
|
165
|
+
projects,
|
|
166
|
+
dependencies,
|
|
167
|
+
policy:
|
|
168
|
+
policyFingerprint === undefined || policyFingerprint === null
|
|
169
|
+
? null
|
|
170
|
+
: { fingerprint: policyFingerprint },
|
|
171
|
+
}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
139
175
|
/**
|
|
140
176
|
* Reads and validates every snapshot in the history directory.
|
|
141
177
|
*
|
|
@@ -732,3 +768,84 @@ function envelopeToSnapshot(envelope, path, id) {
|
|
|
732
768
|
id,
|
|
733
769
|
};
|
|
734
770
|
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* The history directory, resolved once from the single positional argument —
|
|
774
|
+
* shared by the CLI entry and the output guard below.
|
|
775
|
+
*
|
|
776
|
+
* @param {{paths: string[]}} options This run's parsed flags.
|
|
777
|
+
* @param {string} cwd The run's working directory.
|
|
778
|
+
* @returns {string}
|
|
779
|
+
*/
|
|
780
|
+
function historyDirFrom(options, cwd) {
|
|
781
|
+
return isAbsolute(options.paths[0]) ? resolve(options.paths[0]) : resolve(cwd, options.paths[0]);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* `history`'s self-footgun guard: writing the history report back into the
|
|
786
|
+
* very directory `history` reads would poison every later run (the report
|
|
787
|
+
* envelope is a `history` envelope, which `parseBaseline` refuses as a
|
|
788
|
+
* non-`graph` snapshot). Declared by the command that owns the law and
|
|
789
|
+
* enforced by the driver's write door; `null` means no refusal.
|
|
790
|
+
*
|
|
791
|
+
* @param {{output: string|null, paths: string[]}} options This run's parsed
|
|
792
|
+
* flags.
|
|
793
|
+
* @param {string} cwd The run's working directory, for relative flag
|
|
794
|
+
* resolution.
|
|
795
|
+
* @returns {string|null} The refusal message, or `null` when the output is
|
|
796
|
+
* safe.
|
|
797
|
+
*/
|
|
798
|
+
export function historyOutputRefusal(options, cwd) {
|
|
799
|
+
if (!options.output) return null;
|
|
800
|
+
// `resolve()` on the absolute branch too — not just the raw path — the
|
|
801
|
+
// same normalization `writeOutputReport` applies, so an absolute
|
|
802
|
+
// `--output` carrying a `..` segment that resolves INTO the history
|
|
803
|
+
// directory cannot slip past this guard unnormalized.
|
|
804
|
+
const outputAbs = isAbsolute(options.output)
|
|
805
|
+
? resolve(options.output)
|
|
806
|
+
: resolve(cwd, options.output);
|
|
807
|
+
const dir = historyDirFrom(options, cwd);
|
|
808
|
+
if (dirname(outputAbs) === dir) {
|
|
809
|
+
return (
|
|
810
|
+
`archkeep: --output '${options.output}' is inside the history directory '${dir}' — ` +
|
|
811
|
+
`writing the report there would be read back as a snapshot on the next run. ` +
|
|
812
|
+
`Write it somewhere else.`
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* `history` as the CLI drives it: the history directory resolved from the
|
|
820
|
+
* single positional argument, then the shared preamble — command context,
|
|
821
|
+
* and under `--capture` the boundary law whose fingerprint the snapshot
|
|
822
|
+
* records — so `../../cli.mjs`'s driver only wires options, IO seams, and
|
|
823
|
+
* where output lands (`./README.md`). The engine this returns from is
|
|
824
|
+
* `historyCommand` above, unchanged.
|
|
825
|
+
*
|
|
826
|
+
* @param {{capture: boolean, config: string|null, paths: string[]}} options
|
|
827
|
+
* This run's parsed flags.
|
|
828
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
829
|
+
* seams a test injects, the same ones `check` takes.
|
|
830
|
+
* @returns {Promise<object>} `historyCommand`'s result, unmodified.
|
|
831
|
+
*/
|
|
832
|
+
export async function history(options, { cwd, readGraph, listFiles }) {
|
|
833
|
+
const dir = historyDirFrom(options, cwd);
|
|
834
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
835
|
+
// The boundary law's fingerprint when the workspace declares one, so a
|
|
836
|
+
// captured snapshot records the policy it was taken under — the same
|
|
837
|
+
// config loading `graph` uses (`resolvePolicy`, profile-aware the same
|
|
838
|
+
// way `check` is), kept in one place so a capture and a standalone
|
|
839
|
+
// `graph` never disagree about the current policy.
|
|
840
|
+
let fingerprint = null;
|
|
841
|
+
if (options.capture) {
|
|
842
|
+
const { config } = await resolvePolicy(options, commandContext, cwd);
|
|
843
|
+
if (config) {
|
|
844
|
+
fingerprint = computePolicyFingerprint(config);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return historyCommand(dir, commandContext, {
|
|
848
|
+
capture: options.capture,
|
|
849
|
+
policyFingerprint: fingerprint,
|
|
850
|
+
});
|
|
851
|
+
}
|
package/src/commands/impact.mjs
CHANGED
|
@@ -40,8 +40,10 @@ import { coverageRefusal, coverageVerdict } from "./coverage-verdict.mjs";
|
|
|
40
40
|
import { jsonEnvelope, renderJson } from "../report/json.mjs";
|
|
41
41
|
import { formatImpactReport } from "../report/impact-text.mjs";
|
|
42
42
|
import { resolveProvenance } from "./provenance.mjs";
|
|
43
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
43
44
|
import { composeImpactStatement } from "./impact-statement.mjs";
|
|
44
45
|
import { computeImpact } from "./impact-reachability.mjs";
|
|
46
|
+
import { resolvePolicy } from "./policy.mjs";
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
49
|
* The reachability walk this command reports, shared with the canonical
|
|
@@ -171,3 +173,24 @@ export function impactCommand(projectName, commandContext, config = null) {
|
|
|
171
173
|
},
|
|
172
174
|
};
|
|
173
175
|
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* `impact` as the CLI drives it: the shared preamble — command context, then
|
|
179
|
+
* the boundary law — resolved here so `../../cli.mjs`'s driver only wires
|
|
180
|
+
* options, IO seams, and where output lands (`./README.md`). The engine this
|
|
181
|
+
* returns from is `impactCommand` above, unchanged.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} projectName The project whose dependents are walked.
|
|
184
|
+
* @param {{config: string|null}} options This run's parsed flags.
|
|
185
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
186
|
+
* seams a test injects, the same ones `check` takes.
|
|
187
|
+
* @returns {Promise<object>} `impactCommand`'s result, unmodified.
|
|
188
|
+
*/
|
|
189
|
+
export async function impact(projectName, options, { cwd, readGraph, listFiles }) {
|
|
190
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
191
|
+
// Load the boundary config when --config is given or when the workspace
|
|
192
|
+
// declares one, so constraint-impact analysis is computed — profile-aware
|
|
193
|
+
// the same way `check` is (`resolvePolicy`).
|
|
194
|
+
const { config } = await resolvePolicy(options, commandContext, cwd);
|
|
195
|
+
return impactCommand(projectName, commandContext, config);
|
|
196
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `inspect` capability facade — the word's verb roster as explicit named
|
|
3
|
+
* re-exports: `graph`, `impact`, `context` (the non-plan read; `--plan`
|
|
4
|
+
* dispatches to `planContext`), `health`.
|
|
5
|
+
*
|
|
6
|
+
* Pure surface module: no judgment, no defaults, no logic — every export is
|
|
7
|
+
* a re-export of a command module. `cli.mjs` routes its verb imports through
|
|
8
|
+
* the facade so the vocabulary's code referent is visible to the import
|
|
9
|
+
* graph, not just to a comment (PD-18,
|
|
10
|
+
* ../../../../docs/architecture/refactor/DECISIONS.md).
|
|
11
|
+
*/
|
|
12
|
+
export { graph } from "./graph.mjs";
|
|
13
|
+
export { impact } from "./impact.mjs";
|
|
14
|
+
export { context } from "./context-command.mjs";
|
|
15
|
+
export { planContext } from "./plan-context-command.mjs";
|
|
16
|
+
export { health } from "./health.mjs";
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
* reporting set. This makes the plan's verdict correct on every provider.
|
|
62
62
|
*/
|
|
63
63
|
import { statSync } from "node:fs";
|
|
64
|
+
import { isAbsolute, resolve } from "node:path";
|
|
64
65
|
import { join } from "node:path";
|
|
65
66
|
|
|
66
67
|
import { INTENT_FILE, loadIntent } from "../architecture-intent/model.mjs";
|
|
@@ -82,6 +83,8 @@ import { buildDependencies, buildProjects, computePolicyFingerprint } from "./gr
|
|
|
82
83
|
import { resolveProvenance } from "./provenance.mjs";
|
|
83
84
|
import { readAdrContext } from "./adr.mjs";
|
|
84
85
|
import { declaresFitness, fitnessForCheck } from "./fitness.mjs";
|
|
86
|
+
import { resolveCommandContext } from "./context.mjs";
|
|
87
|
+
import { resolvePolicy } from "./policy.mjs";
|
|
85
88
|
import { computeWaivers } from "./waivers.mjs";
|
|
86
89
|
import { declaredFitnessNames, unresolvedDecisionRefRows } from "../governance/adr-registry.mjs";
|
|
87
90
|
import { formatPlanContextReport } from "../report/plan-context-text.mjs";
|
|
@@ -667,3 +670,35 @@ function goWorkResult(goWork) {
|
|
|
667
670
|
function tsconfigPathsResult(tsconfigPaths) {
|
|
668
671
|
return tsconfigPaths === null ? null : { checked: true, findings: tsconfigPaths.findings };
|
|
669
672
|
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* `context --plan` as the CLI drives it: the shared preamble — command
|
|
676
|
+
* context, then the boundary law — resolved here, plus the plan's own
|
|
677
|
+
* arguments (the project, the change's scope paths, the optional history
|
|
678
|
+
* directory), so `../../cli.mjs`'s driver only wires options, IO seams, and
|
|
679
|
+
* where output lands (`./README.md`). The engine this returns from is
|
|
680
|
+
* `planContextCommand` above, unchanged.
|
|
681
|
+
*
|
|
682
|
+
* @param {{config: string|null, historyDir: string|null, paths: string[]}} options
|
|
683
|
+
* This run's parsed flags; `paths[0]` is the project name, the rest are
|
|
684
|
+
* the change's scope.
|
|
685
|
+
* @param {{cwd: string, readGraph?: Function, listFiles?: Function}} io The
|
|
686
|
+
* seams a test injects, the same ones `check` takes.
|
|
687
|
+
* @returns {Promise<object>} `planContextCommand`'s result, unmodified.
|
|
688
|
+
*/
|
|
689
|
+
export async function planContext(options, { cwd, readGraph, listFiles }) {
|
|
690
|
+
const commandContext = resolveCommandContext({ cwd }, { readGraph, listFiles });
|
|
691
|
+
const { config } = await resolvePolicy(options, commandContext, cwd);
|
|
692
|
+
const historyDir = options.historyDir
|
|
693
|
+
? isAbsolute(options.historyDir)
|
|
694
|
+
? options.historyDir
|
|
695
|
+
: resolve(cwd, options.historyDir)
|
|
696
|
+
: null;
|
|
697
|
+
return planContextCommand(
|
|
698
|
+
options.paths[0],
|
|
699
|
+
options.paths.slice(1),
|
|
700
|
+
commandContext,
|
|
701
|
+
config,
|
|
702
|
+
historyDir,
|
|
703
|
+
);
|
|
704
|
+
}
|