@goodbones/cli 0.1.0-beta.14 → 0.1.0-beta.16

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.
@@ -35,6 +35,7 @@ export type CoverageReport = Readonly<Record<CoverageFamily, {
35
35
  readonly covered: number;
36
36
  readonly total: number;
37
37
  readonly floor?: number;
38
+ readonly ceiling?: number;
38
39
  }>>;
39
40
  export type ConformanceReport = Readonly<Record<ConformanceMeasure, {
40
41
  readonly count: number;
@@ -43,6 +44,7 @@ export type ConformanceReport = Readonly<Record<ConformanceMeasure, {
43
44
  export type CheckReport = {
44
45
  readonly version: 1;
45
46
  readonly files: number;
47
+ readonly outside: number;
46
48
  readonly roots: ReadonlyArray<string>;
47
49
  readonly ok: boolean;
48
50
  readonly manifest: {
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,kBAAkB,EAEvB,KAAK,cAAc,EA2BpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAKL,KAAK,kBAAkB,EAEvB,KAAK,cAAc,EAqBnB,KAAK,KAAK,EAQV,KAAK,YAAY,EAOjB,KAAK,QAAQ,EAMb,KAAK,SAAS,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,KAAK,YAAY,EAAsC,MAAM,oBAAoB,CAAC;AAiB3F,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAMnF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IAK9C,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;IACtD,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAIvB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IAE5C,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAG3B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,eAAe,WAClB,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,YACnB,cAAc,KACtB,QA2GF,CAAC;AA4BF,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAG5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAG3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAKF,MAAM,MAAM,cAAc,GAAG,QAAQ,CACnC,MAAM,CACJ,cAAc,EACd;IACE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CACF,CACF,CAAC;AAKF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CACtC,MAAM,CAAC,kBAAkB,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAClF,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAGvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IAGrB,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAEtE,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACtD,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;IAEnD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAC7C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KACzC,CAAC;IACF,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;CACnD,CAAC;AAkBF,eAAO,MAAM,WAAW,WACd,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,gBACd,MAAM,KACnB,WAA2F,CAAC;AAwU/F,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,KAAK,WACR,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,WACnB,YAAY,KACpB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAU7B,CAAC;AAYL,eAAO,MAAM,UAAU,WACb,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,gBACd,MAAM,KACnB,QAyCF,CAAC;AA4HF,MAAM,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAM9C,eAAO,MAAM,WAAW,WACd,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,WACnB,kBAAkB,KAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAM7B,CAAC;AAKL,eAAO,MAAM,QAAQ,WACX,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAqC7B,CAAC;AAEL,eAAO,MAAM,aAAa,WAChB,YAAY,SACb,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAkB7B,CAAC;AAIL,eAAO,MAAM,OAAO,WACV,YAAY,QACd,MAAM,UACL,aAAa,CAAC,MAAM,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CA+I7B,CAAC;AAQL,eAAO,MAAM,KAAK,WACR,YAAY,QACd,MAAM,WACJ,MAAM,GAAG,MAAM,KACtB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAmD7B,CAAC;AA8EL,eAAO,MAAM,IAAI,aAAc,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAoBlE,CAAC;AAKL,eAAO,MAAM,OAAO,aACR,MAAM,mBACC,MAAM,KACtB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CA0C7B,CAAC;AAyDL,eAAO,MAAM,cAAc,SACnB,aAAa,CAAC,MAAM,CAAC,OACtB,aAAa,CAAC,MAAM,CAAC,KACzB;IACD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CA8BvC,CAAC;AAIF,eAAO,MAAM,eAAe,SACpB,aAAa,CAAC,MAAM,CAAC,OACtB,aAAa,CAAC,MAAM,CAAC,KACzB;IACD,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IACvE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAcvC,CAAC;AAsCF,eAAO,MAAM,UAAU,WACb,YAAY,gBACN,aAAa,CAAC,MAAM,CAAC,QAC7B,aAAa,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CA0N7B,CAAC;AAEL,eAAO,MAAM,SAAS,WACZ,YAAY,gBACN,aAAa,CAAC,MAAM,CAAC,QAC7B,aAAa,CAAC,MAAM,CAAC,mBACV,MAAM,KACtB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAmO7B,CAAC;AAYL,eAAO,MAAM,GAAG,aACJ,MAAM,QACV,aAAa,CAAC,MAAM,CAAC,mBAEV,MAAM,KACtB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAiG7B,CAAC;AAEL,eAAO,MAAM,WAAW,kCAAgB,CAAC"}
package/build/esm/run.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import * as path from "node:path";
4
- import { attest, authorOf, baseSideAt, campaignFailuresOf, campaignReportsOf, campaignsOf, campaignsSelecting, clear, concede, evaluateCampaigns, explainCampaignLines, explainObjective, historyOf, hitsInWindow, ledgeredFilter, measureFile, note, nudgeOf, readDiff, renderCampaignReports, renderCampaignRows, renderHistory, renderNudge, reportSpecsOf, snapshotCampaignsOf, valueOfParts, widenedExtensions, } from "@goodbones/campaigns";
5
- import { baselineOf, breaches, CONFORMANCE_MEASURES, coverageOf, cyclesIn, decodeBaseline, decodeManifest, EMPTY_BASELINE, evaluateGraph, evaluateMemberSite, evaluateResolvedEdge, evaluateSelectedBindings, evaluateStructure, evaluateSurface, exportRulesSelecting, findManifestFile, fingerprintOf, formatManifestYaml, formatMessage, fractionsOf, hasGraphRules, heightOf, listSourceFiles, makeBaselineFilter, MANIFEST_FILENAMES, MANIFEST_SCHEMA_ID, memberRulesSelecting, readManifestFile, requiredSiblingsOf, residueOf, rulesSelecting, serializeBaseline, slackOf, SNAPSHOT_VERSION, staleEntriesOf, surfaceRulesSelecting, vacancyOf, } from "@goodbones/core";
4
+ import { attest, authorOf, baseSideAt, campaignFailuresOf, campaignReportsOf, campaignsOf, campaignsSelecting, clear, concede, evaluateCampaigns, explainCampaignLines, explainObjective, historyOf, hitsInWindow, ledgeredFilter, measureFile, note, nudgeOf, readDiff, renderCampaignReports, renderCampaignRows, renderHistory, renderNudge, renderSectorView, reportSpecsOf, sectorMovesOf, sectorViewOf, snapshotCampaignsOf, valueOfParts, widenedExtensions, } from "@goodbones/campaigns";
5
+ import { baselineOf, breaches, CONFORMANCE_MEASURES, countedFiles, coverageOf, cyclesIn, decodeBaseline, decodeManifest, EMPTY_BASELINE, evaluateGraph, evaluateMemberSite, evaluateResolvedEdge, evaluateSelectedBindings, evaluateStructure, evaluateSurface, expandAliases, exportRulesSelecting, findManifestFile, fingerprintOf, formatManifestYaml, formatMessage, fractionsOf, globsOf, globToRegExp, hasGraphRules, heightOf, listSourceFiles, makeBaselineFilter, MANIFEST_FILENAMES, MANIFEST_SCHEMA_ID, memberRulesSelecting, readManifestFile, requiredSiblingsOf, residueOf, rulesSelecting, serializeBaseline, slackOf, SNAPSHOT_VERSION, staleEntriesOf, surfaceRulesSelecting, vacancyOf, } from "@goodbones/core";
6
6
  import * as Effect from "effect/Effect";
7
7
  import * as Result from "effect/Result";
8
8
  import { loadPolicyFromFile, manifestPathOf } from "./config-loader.js";
@@ -140,13 +140,18 @@ const sha256Of = (file) => {
140
140
  }
141
141
  };
142
142
  export const checkReport = (policy, roots, manifestPath) => reportOf(policy, roots, manifestPath, collectFindings(policy, roots)).report;
143
+ // The patterns the policy's `limits.outside` names, alias-expanded, each a
144
+ // subtree.
145
+ const outsideOf = (policy) => globsOf(policy.config.limits?.outside ?? []).map((glob) => globToRegExp(expandAliases(glob, policy.config.aliases ?? {})));
143
146
  const measuresOf = (policy, files, edges) => {
144
147
  // Slack is measured over the walked files as well as the edges: an
145
148
  // allowlist that selects no file is vacant, and its entries are reported as
146
149
  // that rather than as lines nobody needs.
147
150
  const { concentration, slack } = slackOf(policy.importRules, edges, files);
148
151
  return {
149
- residue: residueOf(policy, files),
152
+ // Residue is a count of files, and counts the ones the limits count. A
153
+ // node is live or vacant by every file under it, counted or not.
154
+ residue: residueOf(policy, countedFiles(files, outsideOf(policy))),
150
155
  vacant: vacancyOf(policy.importRules, files),
151
156
  slack,
152
157
  concentration,
@@ -188,22 +193,29 @@ const reportOf = (policy, roots, manifestPath, findings) => {
188
193
  const campaigns = campaignReportsOf(policy, findings.campaigns);
189
194
  // The floors. A policy states how much of the tree it reaches, per
190
195
  // family; falling under is a policy that quietly stopped covering files.
196
+ // Both are held over the files the limits count: what `limits.outside`
197
+ // names is judged by every rule and left out of these numbers.
191
198
  const floors = policy.config.limits?.coverage ?? {};
199
+ const unreached = policy.config.limits?.unreached ?? {};
192
200
  const files = listSourceFiles(policy.repoRoot, roots, policy.languages);
193
- const found = coverageOf(policy, files);
201
+ const counted = countedFiles(files, outsideOf(policy));
202
+ const found = coverageOf(policy, counted);
194
203
  const covered = (family) => family === "structure" ? found.structure.enumerated : found[family].covered;
195
204
  const coverage = Object.fromEntries(COVERAGE_FAMILIES.map((family) => {
196
205
  const floor = floors[family];
206
+ const ceiling = unreached[family];
197
207
  return [
198
208
  family,
199
209
  {
200
210
  covered: covered(family),
201
211
  total: found.files,
202
212
  ...(floor === undefined ? {} : { floor }),
213
+ ...(ceiling === undefined ? {} : { ceiling }),
203
214
  },
204
215
  ];
205
216
  }));
206
217
  const shortfalls = shortfallsOf(coverage);
218
+ const overruns = overrunsOf(coverage);
207
219
  // The ceilings. What no family reaches, what no file is under and what
208
220
  // nothing imports through are each a count the policy may hold itself
209
221
  // to; rising over one is a manifest that quietly widened.
@@ -219,11 +231,13 @@ const reportOf = (policy, roots, manifestPath, findings) => {
219
231
  const report = {
220
232
  version: 1,
221
233
  files: findings.files,
234
+ outside: files.length - counted.length,
222
235
  roots,
223
236
  ok: reportable === 0 &&
224
237
  findings.unresolved.length === 0 &&
225
238
  stale.length === 0 &&
226
239
  shortfalls.length === 0 &&
240
+ overruns.length === 0 &&
227
241
  excesses.length === 0 &&
228
242
  campaignFailuresOf(campaigns).length === 0,
229
243
  manifest: {
@@ -250,6 +264,14 @@ const shortfallsOf = (coverage) => COVERAGE_FAMILIES.flatMap((family) => {
250
264
  ? []
251
265
  : [{ family, actual, floor }];
252
266
  });
267
+ const overrunsOf = (coverage) => COVERAGE_FAMILIES.flatMap((family) => {
268
+ const { ceiling, covered, total } = coverage[family];
269
+ const unreached = total - covered;
270
+ return ceiling === undefined ||
271
+ !breaches({ direction: "down", limit: ceiling, tolerance: 0 }, unreached)
272
+ ? []
273
+ : [{ family, unreached, ceiling }];
274
+ });
253
275
  const excessesOf = (conformance) => CONFORMANCE_MEASURES.flatMap((measure) => {
254
276
  const { ceiling, count } = conformance[measure];
255
277
  return ceiling === undefined ||
@@ -265,6 +287,8 @@ const failureOf = (report, shortfalls) => {
265
287
  return fail(campaignFailure);
266
288
  if (shortfalls.length > 0)
267
289
  return fail("coverage below floor");
290
+ if (overrunsOf(report.coverage).length > 0)
291
+ return fail("unreached above ceiling");
268
292
  if (excessesOf(report.conformance).length > 0)
269
293
  return fail("conformance above ceiling");
270
294
  if (report.ok)
@@ -303,6 +327,7 @@ const renderText = (report) => {
303
327
  const reportable = report.violations.filter((one) => one.kind !== "campaign" && !one.baselined);
304
328
  const carried = report.violations.filter((one) => one.kind !== "campaign").length - reportable.length;
305
329
  const shortfalls = shortfallsOf(report.coverage);
330
+ const overruns = overrunsOf(report.coverage);
306
331
  const excesses = excessesOf(report.conformance);
307
332
  return [
308
333
  ...reportable.map(describe),
@@ -330,6 +355,15 @@ const renderText = (report) => {
330
355
  "",
331
356
  " architecture coverage # which files no rule reaches",
332
357
  ]),
358
+ ...(overruns.length === 0
359
+ ? []
360
+ : [
361
+ "",
362
+ "more files are unreached than the ceiling the policy states for itself:",
363
+ ...overruns.map((one) => ` ${one.family}: ${count(one.unreached, "file")} unreached, ceiling ${String(one.ceiling)}`),
364
+ "",
365
+ " architecture coverage # which files no rule reaches",
366
+ ]),
333
367
  ...(excesses.length === 0
334
368
  ? []
335
369
  : [
@@ -379,6 +413,7 @@ export const snapshotOf = (policy, roots, manifestPath) => {
379
413
  manifest: report_.manifest,
380
414
  roots: report_.roots,
381
415
  files: report_.files,
416
+ outside: report_.outside,
382
417
  ok: report_.ok,
383
418
  coverage: report_.coverage,
384
419
  conformance: report_.conformance,
@@ -395,18 +430,29 @@ export const snapshotOf = (policy, roots, manifestPath) => {
395
430
  campaigns,
396
431
  };
397
432
  };
433
+ // The unreached ceiling beside a family's row, and the ratchet's nudge when
434
+ // the count has fallen under it: a ceiling is lowered by hand.
435
+ const unreachedMark = (covered, total, ceiling) => {
436
+ if (ceiling === undefined)
437
+ return "";
438
+ const left = total - covered;
439
+ if (left > ceiling)
440
+ return ` ${String(left)} unreached > ${String(ceiling)} ✗`;
441
+ return ` ${String(left)} unreached ≤ ${String(ceiling)} ✓${left < ceiling ? `, lower it to ${String(left)}` : ""}`;
442
+ };
443
+ const outsideNote = (outside) => outside === 0 ? "" : `, ${count(outside, "file")} outside the limits`;
398
444
  const renderSnapshot = (snapshot) => {
399
445
  const reportable = snapshot.violations.filter((one) => !one.baselined && !one.ledgered);
400
446
  const carried = snapshot.violations.length - reportable.length;
401
447
  const row = (family) => {
402
- const { covered, floor, total } = snapshot.coverage[family];
448
+ const { ceiling, covered, floor, total } = snapshot.coverage[family];
403
449
  const fraction = total === 0 ? 1 : covered / total;
404
450
  const mark = floor === undefined
405
451
  ? ""
406
452
  : fraction >= floor
407
453
  ? ` ≥ ${percent(floor)} ✓`
408
454
  : ` < ${percent(floor)} ✗`;
409
- return ` ${family.padEnd(10)} ${String(covered).padStart(5)}/${String(total)} ${percent(fraction).padStart(4)}${mark}`;
455
+ return ` ${family.padEnd(10)} ${String(covered).padStart(5)}/${String(total)} ${percent(fraction).padStart(4)}${mark}${unreachedMark(covered, total, ceiling)}`;
410
456
  };
411
457
  const section = (title, lines) => [
412
458
  "",
@@ -428,7 +474,7 @@ const renderSnapshot = (snapshot) => {
428
474
  return ` ≤ ${String(ceiling)} ✓${actual < ceiling ? `, lower it to ${String(actual)}` : ""}`;
429
475
  };
430
476
  return [
431
- `${String(snapshot.files)} files under ${snapshot.roots.join(", ")}, against ${snapshot.manifest.path}`,
477
+ `${String(snapshot.files)} files under ${snapshot.roots.join(", ")}, against ${snapshot.manifest.path}${outsideNote(snapshot.outside)}`,
432
478
  ...section("coverage", COVERAGE_FAMILIES.map(row)),
433
479
  ...section(`residue: ${count(snapshot.residue.files.length, "file")} no family reaches` +
434
480
  (snapshot.residue.folders.length === 0
@@ -485,10 +531,14 @@ export const conformance = (policy, roots, options) => Effect.gen(function* () {
485
531
  // this is whether the files are there to fire on. Reported per family, with the
486
532
  // adoption backlog — the tiers that said "not tightened yet" — beneath it.
487
533
  export const coverage = (policy, roots) => Effect.gen(function* () {
488
- const files = listSourceFiles(policy.repoRoot, roots, policy.languages);
534
+ // The limits are held over the files they count, so that is what the
535
+ // rows are over: what `limits.outside` names is said, and left out.
536
+ const walked = listSourceFiles(policy.repoRoot, roots, policy.languages);
537
+ const files = countedFiles(walked, outsideOf(policy));
489
538
  const found = coverageOf(policy, files);
490
539
  const fractions = fractionsOf(found);
491
540
  const floors = policy.config.limits?.coverage ?? {};
541
+ const ceilings = policy.config.limits?.unreached ?? {};
492
542
  const row = (family, covered, note) => {
493
543
  const floor = floors[family];
494
544
  const mark = floor === undefined
@@ -496,10 +546,10 @@ export const coverage = (policy, roots) => Effect.gen(function* () {
496
546
  : fractions[family] >= floor
497
547
  ? ` ≥ ${percent(floor)} ✓`
498
548
  : ` < ${percent(floor)} ✗`;
499
- return ` ${family.padEnd(10)} ${String(covered).padStart(5)}/${String(found.files)} ${percent(fractions[family]).padStart(4)} ${note}${mark}`;
549
+ return ` ${family.padEnd(10)} ${String(covered).padStart(5)}/${String(found.files)} ${percent(fractions[family]).padStart(4)} ${note}${mark}${unreachedMark(covered, found.files, ceilings[family])}`;
500
550
  };
501
551
  yield* report([
502
- `${String(found.files)} files under ${roots.join(", ")}`,
552
+ `${String(walked.length)} files under ${roots.join(", ")}${outsideNote(walked.length - files.length)}`,
503
553
  "",
504
554
  row("imports", found.imports.covered, "under an import allowlist"),
505
555
  row("structure", found.structure.enumerated, `in an enumerated folder (${String(found.structure.open)} in an open one, ${String(found.structure.total - found.structure.enumerated - found.structure.open)} in none)`),
@@ -596,7 +646,7 @@ export const explain = (policy, file, roots = ["packages"]) => Effect.gen(functi
596
646
  ? `${String(parts.of)} of ${String(parts.per)} here`
597
647
  : `${String(valueOfParts(measure, parts))} here`;
598
648
  return [
599
- ` ${objective.name} — ${firstSentence(objective.why ?? objective.message)} (measure, ${objective.direction}; ${here})`,
649
+ ` ${objective.name} — ${firstSentence(objective.intent ?? objective.message)} (measure, ${objective.direction}; ${here})`,
600
650
  ];
601
651
  }
602
652
  const table = explainObjective(objective, input);
@@ -604,7 +654,7 @@ export const explain = (policy, file, roots = ["packages"]) => Effect.gen(functi
604
654
  return [];
605
655
  const fired = table.length > 0 && table.every((line) => line.answer);
606
656
  return [
607
- ` ${objective.name} — ${firstSentence(objective.why ?? objective.message)} (${objective.holdout}; ${fired ? "fires here" : "no hit"})`,
657
+ ` ${objective.name} — ${firstSentence(objective.intent ?? objective.message)} (${objective.holdout}; ${fired ? "fires here" : "no hit"})`,
608
658
  ...table.map((line) => ` ${line.answer ? "✓" : "✗"} ${line.term}${line.count === undefined ? "" : ` (${String(line.count)})`}`),
609
659
  ];
610
660
  }),
@@ -740,6 +790,7 @@ limits:
740
790
  # perimeter: file
741
791
  # objectives:
742
792
  # is-ts:
793
+ # intent: A JavaScript file is one the strict compiler never checks.
743
794
  # holdout: file
744
795
  # match: { path: { file: "\\\\.(js|jsx)$" } }
745
796
  # probes: { fires: [{ path: src/legacy/util.js }], ignores: [{ path: src/util.ts }] }
@@ -965,6 +1016,9 @@ export const objectives = (policy, defaultRoots, argv) => Effect.gen(function* (
965
1016
  try: () => clear(policy, evaluation, only, by),
966
1017
  catch: (cause) => fail(String(cause)),
967
1018
  });
1019
+ // Where each sector went, read off the ledgers as they stood: a
1020
+ // clear of one objective places no sector, so it says nothing.
1021
+ const moves = only === null ? sectorMovesOf(policy, evaluation) : [];
968
1022
  for (const outcome of outcomes) {
969
1023
  const parts = [
970
1024
  ...(outcome.entered.length > 0
@@ -997,6 +1051,13 @@ export const objectives = (policy, defaultRoots, argv) => Effect.gen(function* (
997
1051
  .join(", ")})`,
998
1052
  ]
999
1053
  : []),
1054
+ ...(outcome.measure.grown.length > 0
1055
+ ? [
1056
+ `${count(outcome.measure.grown.length, "sector")} grown (${outcome.measure.grown
1057
+ .map((one) => `${one.sector} ${String(one.from)} → ${String(one.to)}, ${one.phase === null ? "measured, not held" : `as ${one.phase} expects`}`)
1058
+ .join(", ")})`,
1059
+ ]
1060
+ : []),
1000
1061
  ...(outcome.closed > 0 ? [`${count(outcome.closed, "sector")} closed`] : []),
1001
1062
  ...(outcome.rebaselined.length > 0
1002
1063
  ? [
@@ -1009,6 +1070,12 @@ export const objectives = (policy, defaultRoots, argv) => Effect.gen(function* (
1009
1070
  }
1010
1071
  lines.push(`${outcome.campaign}/${outcome.objective}: ${parts.length === 0 ? "nothing to clear" : parts.join(", ")}; ${count(outcome.left, "holdout")} left.`);
1011
1072
  }
1073
+ for (const move of moves) {
1074
+ lines.push(`${move.campaign}: ${move.sector} ${move.back ? "went back" : "moved"} ${move.from ?? "done"} → ${move.to ?? "done"}` +
1075
+ (move.passed.length === 0
1076
+ ? "."
1077
+ : `, passing ${move.passed.join(", ")} in the same clear: nothing there was ever counted for it.`));
1078
+ }
1012
1079
  }
1013
1080
  return yield* report(lines);
1014
1081
  }
@@ -1046,6 +1113,18 @@ export const objectives = (policy, defaultRoots, argv) => Effect.gen(function* (
1046
1113
  });
1047
1114
  if (Result.isFailure(outcome))
1048
1115
  return yield* Effect.fail(fail(outcome.failure));
1116
+ // A concession that sends a sector back a phase says so, and names
1117
+ // the attestations it revoked on the way.
1118
+ const sentBack = outcome.success.sentBack.flatMap((one) => [
1119
+ "",
1120
+ `this concession moves ${one.sector} back ${one.from ?? "done"} → ${one.to ?? "done"}.`,
1121
+ ...(one.revoked.length === 0
1122
+ ? []
1123
+ : [
1124
+ `It revokes ${one.sector}'s attestation of ${one.revoked.join(", ")}: what was attested no longer holds. Once it does again, attest it anew:`,
1125
+ ...one.revoked.map((phase) => ` architecture campaigns attest ${one.sector} ${phase} --reason "…" --campaign ${rule.id}`),
1126
+ ]),
1127
+ ]);
1049
1128
  const scalar = rule.objectives.find((one) => one.id === objective.success)?.measure !== null;
1050
1129
  if (scalar) {
1051
1130
  if (outcome.success.conceded.length === 0) {
@@ -1062,6 +1141,7 @@ export const objectives = (policy, defaultRoots, argv) => Effect.gen(function* (
1062
1141
  "",
1063
1142
  `${count(outcome.success.left.length, "sector")} left past the record; check still fails on them.`,
1064
1143
  ]),
1144
+ ...sentBack,
1065
1145
  ]);
1066
1146
  }
1067
1147
  if (outcome.success.conceded.length === 0) {
@@ -1078,6 +1158,7 @@ export const objectives = (policy, defaultRoots, argv) => Effect.gen(function* (
1078
1158
  "",
1079
1159
  `${count(outcome.success.left.length, "hit")} left unrecorded; check still fails on them.`,
1080
1160
  ]),
1161
+ ...sentBack,
1081
1162
  ]);
1082
1163
  }
1083
1164
  default:
@@ -1094,15 +1175,53 @@ export const campaigns = (policy, defaultRoots, argv, configFilename) => Effect.
1094
1175
  const retired = parsed.subcommand === undefined ? undefined : RETIRED[parsed.subcommand];
1095
1176
  if (retired !== undefined)
1096
1177
  return yield* Effect.fail(fail(retired));
1097
- switch (parsed.subcommand) {
1178
+ // A positional that is no path is refused, not walked: most often it is
1179
+ // a sector's name, and `--sector` is what asks about one.
1180
+ for (const root of parsed.roots) {
1181
+ if (existsSync(path.resolve(policy.repoRoot, root)))
1182
+ continue;
1183
+ const named = collectFindings(policy, defaultRoots).campaigns.some((one) => one.sectors.has(root));
1184
+ return yield* Effect.fail(fail(`campaigns: ${root} is not a path in the repository, so there is nothing to walk there.` +
1185
+ (named ? ` It names a sector: \`campaigns status --sector ${root}\`.` : "")));
1186
+ }
1187
+ const sector = flagOf(argv, "--sector");
1188
+ if (parsed.subcommand === "status" && sector !== undefined) {
1189
+ const only = argv.includes("--campaign") ? campaignFor(policy, argv) : null;
1190
+ if (only !== null && Result.isFailure(only))
1191
+ return yield* Effect.fail(fail(only.failure));
1192
+ const views = collectFindings(policy, roots)
1193
+ .campaigns.filter((one) => only === null || one.rule.id === only.success.id)
1194
+ .flatMap((one) => {
1195
+ const view = sectorViewOf(policy, one, sector);
1196
+ return view === null ? [] : [view];
1197
+ });
1198
+ if (views.length === 0) {
1199
+ return yield* Effect.fail(fail(`campaigns: no campaign under ${roots.join(", ")} has a sector named ${sector}.`));
1200
+ }
1201
+ return yield* report(json
1202
+ ? [JSON.stringify({ version: 1, sectors: views }, null, 2)]
1203
+ : views.flatMap((view, i) => [...(i === 0 ? [] : [""]), ...renderSectorView(view)]));
1204
+ }
1205
+ // `status` without `--changed` is the overview, in text or JSON: the
1206
+ // nudge alone is scoped to a diff.
1207
+ const subcommand = parsed.subcommand === "status" && !argv.includes("--changed") ? undefined : parsed.subcommand;
1208
+ switch (subcommand) {
1098
1209
  case undefined: {
1099
1210
  const snapshot = snapshotOf(policy, roots, manifestPathOf(policy.repoRoot, configFilename));
1211
+ if (json) {
1212
+ // Each campaign as the conformance snapshot carries it: its
1213
+ // burn-down, its ladder, and a row per sector.
1214
+ return yield* report([
1215
+ JSON.stringify({ version: 1, campaigns: snapshot.campaigns }, null, 2),
1216
+ ]);
1217
+ }
1100
1218
  return yield* report([
1101
1219
  `${count(snapshot.campaigns.length, "campaign")} under ${roots.join(", ")}`,
1102
1220
  "",
1103
1221
  ...renderCampaignRows(snapshot.campaigns),
1104
1222
  "",
1105
1223
  " architecture campaigns status --changed [--base <ref>] [--json] # what a diff touches, and what to do",
1224
+ " architecture campaigns status --sector <sector> [--json] # where one sector stands, and what holds it",
1106
1225
  " architecture objectives clear [<campaign>[/<objective>]] # reconcile the ledgers with the code",
1107
1226
  ' architecture objectives concede <campaign>[/<objective>] --reason "<why>" # record why a count may rise',
1108
1227
  ' architecture campaigns attest <sector> <phase> --reason "<why>" [--evidence <url>]',
@@ -1111,9 +1230,6 @@ export const campaigns = (policy, defaultRoots, argv, configFilename) => Effect.
1111
1230
  ]);
1112
1231
  }
1113
1232
  case "status": {
1114
- if (!argv.includes("--changed")) {
1115
- return yield* Effect.fail(fail("campaigns status takes --changed: the nudge is scoped to a diff."));
1116
- }
1117
1233
  const base = flagOf(argv, "--base") ?? null;
1118
1234
  const diff = yield* Effect.try({
1119
1235
  try: () => readDiff(policy.repoRoot, base),
@@ -1126,11 +1242,26 @@ export const campaigns = (policy, defaultRoots, argv, configFilename) => Effect.
1126
1242
  try: () => baseSideAt(policy, base, roots, (repoRoot) => loadPolicyFromFile(repoRoot, configFilename)),
1127
1243
  catch: (cause) => fail(`could not evaluate the base tree at ${base}: ${String(cause)}`),
1128
1244
  });
1245
+ // The ledger mode's "before" for work done ahead of the plan is the
1246
+ // HEAD tree, evaluated once per commit and cached: no ledger counts
1247
+ // the holdouts of a phase a sector has not entered.
1248
+ const headTree = base !== null || !current.some((one) => one.rule.phases.length > 0)
1249
+ ? null
1250
+ : yield* Effect.tryPromise({
1251
+ try: () => baseSideAt(policy, "HEAD", roots, (repoRoot) => loadPolicyFromFile(repoRoot, configFilename)),
1252
+ catch: (cause) => fail(`could not evaluate the tree at HEAD: ${String(cause)}`),
1253
+ });
1129
1254
  const hotfix = flagOf(argv, "--hotfix") ?? null;
1130
- const nudge = nudgeOf(policy, current, diff, baseSide, hotfix, hotfix === null ? null : authorOf(flagOf(argv, "--by")));
1255
+ const nudge = nudgeOf(policy, current, diff, baseSide, hotfix, hotfix === null ? null : authorOf(flagOf(argv, "--by")), headTree);
1131
1256
  yield* report(json ? [JSON.stringify(nudge, null, 2)] : renderNudge(nudge, policy.now));
1132
- if (!nudge.ok)
1133
- return yield* Effect.fail(fail("the diff sends a sector back under its onTouch"));
1257
+ if (!nudge.ok) {
1258
+ const ahead = nudge.sectors.some((one) => one.verdict === "ahead");
1259
+ const back = nudge.sectors.some((one) => one.verdict !== "ok" && one.verdict !== "ahead" && one.verdict !== "hotfix");
1260
+ return yield* Effect.fail(fail([
1261
+ ...(back ? ["the diff sends a sector back under its onTouch"] : []),
1262
+ ...(ahead ? ["the diff works ahead of a sector's phase under its onAhead"] : []),
1263
+ ].join("; ")));
1264
+ }
1134
1265
  return;
1135
1266
  }
1136
1267
  case "attest": {