@goodbones/core 0.1.0-beta.10 → 0.1.0-beta.12

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 (119) hide show
  1. package/build/dts/core/structure.d.ts.map +1 -1
  2. package/build/dts/domain/architecture-config.d.ts +3 -310
  3. package/build/dts/domain/architecture-config.d.ts.map +1 -1
  4. package/build/dts/domain/snapshot.d.ts +150 -18
  5. package/build/dts/domain/snapshot.d.ts.map +1 -1
  6. package/build/dts/index.d.ts +11 -14
  7. package/build/dts/index.d.ts.map +1 -1
  8. package/build/dts/infrastructure/file-system-fake.d.ts.map +1 -1
  9. package/build/dts/infrastructure/file-system-live.d.ts.map +1 -1
  10. package/build/dts/infrastructure/walk.d.ts +6 -1
  11. package/build/dts/infrastructure/walk.d.ts.map +1 -1
  12. package/build/dts/load/extension.d.ts +33 -0
  13. package/build/dts/load/extension.d.ts.map +1 -0
  14. package/build/dts/load/policy.d.ts +4 -10
  15. package/build/dts/load/policy.d.ts.map +1 -1
  16. package/build/dts/manifest/compile.d.ts +7 -2
  17. package/build/dts/manifest/compile.d.ts.map +1 -1
  18. package/build/dts/manifest/extension.d.ts +8 -0
  19. package/build/dts/manifest/extension.d.ts.map +1 -0
  20. package/build/dts/manifest/glob.d.ts +1 -0
  21. package/build/dts/manifest/glob.d.ts.map +1 -1
  22. package/build/dts/manifest/json-schema.d.ts +6 -2
  23. package/build/dts/manifest/json-schema.d.ts.map +1 -1
  24. package/build/dts/manifest/manifest.d.ts +7 -362
  25. package/build/dts/manifest/manifest.d.ts.map +1 -1
  26. package/build/dts/ports/file-system.d.ts +1 -0
  27. package/build/dts/ports/file-system.d.ts.map +1 -1
  28. package/build/dts/testing.d.ts +0 -1
  29. package/build/dts/testing.d.ts.map +1 -1
  30. package/build/esm/core/structure.js +1 -1
  31. package/build/esm/core/structure.js.map +1 -1
  32. package/build/esm/domain/architecture-config.js +3 -136
  33. package/build/esm/domain/architecture-config.js.map +1 -1
  34. package/build/esm/domain/snapshot.js +55 -15
  35. package/build/esm/domain/snapshot.js.map +1 -1
  36. package/build/esm/index.js +16 -10
  37. package/build/esm/index.js.map +1 -1
  38. package/build/esm/infrastructure/file-system-fake.js +10 -0
  39. package/build/esm/infrastructure/file-system-fake.js.map +1 -1
  40. package/build/esm/infrastructure/file-system-live.js +9 -1
  41. package/build/esm/infrastructure/file-system-live.js.map +1 -1
  42. package/build/esm/infrastructure/walk.js +50 -4
  43. package/build/esm/infrastructure/walk.js.map +1 -1
  44. package/build/esm/load/extension.js +2 -0
  45. package/build/esm/load/extension.js.map +1 -0
  46. package/build/esm/load/policy.js +43 -116
  47. package/build/esm/load/policy.js.map +1 -1
  48. package/build/esm/manifest/compile.js +3 -182
  49. package/build/esm/manifest/compile.js.map +1 -1
  50. package/build/esm/manifest/extension.js +2 -0
  51. package/build/esm/manifest/extension.js.map +1 -0
  52. package/build/esm/manifest/glob.js +3 -0
  53. package/build/esm/manifest/glob.js.map +1 -1
  54. package/build/esm/manifest/json-schema.js +45 -11
  55. package/build/esm/manifest/json-schema.js.map +1 -1
  56. package/build/esm/manifest/manifest.js +56 -172
  57. package/build/esm/manifest/manifest.js.map +1 -1
  58. package/build/esm/testing.js +0 -1
  59. package/build/esm/testing.js.map +1 -1
  60. package/package.json +1 -1
  61. package/schema/architecture-node.schema.json +0 -887
  62. package/schema/architecture.schema.json +3264 -1591
  63. package/schema/conformance.schema.json +291 -47
  64. package/src/core/structure.ts +5 -3
  65. package/src/domain/architecture-config.ts +3 -180
  66. package/src/domain/snapshot.ts +125 -25
  67. package/src/index.ts +51 -78
  68. package/src/infrastructure/file-system-fake.ts +12 -0
  69. package/src/infrastructure/file-system-live.ts +8 -1
  70. package/src/infrastructure/walk.ts +58 -3
  71. package/src/load/extension.ts +66 -0
  72. package/src/load/policy.ts +60 -175
  73. package/src/manifest/compile.ts +2 -213
  74. package/src/manifest/extension.ts +41 -0
  75. package/src/manifest/glob.ts +5 -0
  76. package/src/manifest/json-schema.ts +53 -11
  77. package/src/manifest/manifest.ts +82 -214
  78. package/src/ports/file-system.ts +3 -0
  79. package/src/testing.ts +0 -1
  80. package/build/dts/core/campaigns.d.ts +0 -141
  81. package/build/dts/core/campaigns.d.ts.map +0 -1
  82. package/build/dts/core/ledger.d.ts +0 -61
  83. package/build/dts/core/ledger.d.ts.map +0 -1
  84. package/build/dts/domain/report.d.ts +0 -16
  85. package/build/dts/domain/report.d.ts.map +0 -1
  86. package/build/dts/infrastructure/campaign-functions.d.ts +0 -7
  87. package/build/dts/infrastructure/campaign-functions.d.ts.map +0 -1
  88. package/build/dts/infrastructure/report-source-fake.d.ts +0 -4
  89. package/build/dts/infrastructure/report-source-fake.d.ts.map +0 -1
  90. package/build/dts/infrastructure/report-source-live.d.ts +0 -3
  91. package/build/dts/infrastructure/report-source-live.d.ts.map +0 -1
  92. package/build/dts/ports/campaign-predicate.d.ts +0 -24
  93. package/build/dts/ports/campaign-predicate.d.ts.map +0 -1
  94. package/build/dts/ports/report-source.d.ts +0 -14
  95. package/build/dts/ports/report-source.d.ts.map +0 -1
  96. package/build/esm/core/campaigns.js +0 -778
  97. package/build/esm/core/campaigns.js.map +0 -1
  98. package/build/esm/core/ledger.js +0 -171
  99. package/build/esm/core/ledger.js.map +0 -1
  100. package/build/esm/domain/report.js +0 -168
  101. package/build/esm/domain/report.js.map +0 -1
  102. package/build/esm/infrastructure/campaign-functions.js +0 -67
  103. package/build/esm/infrastructure/campaign-functions.js.map +0 -1
  104. package/build/esm/infrastructure/report-source-fake.js +0 -6
  105. package/build/esm/infrastructure/report-source-fake.js.map +0 -1
  106. package/build/esm/infrastructure/report-source-live.js +0 -165
  107. package/build/esm/infrastructure/report-source-live.js.map +0 -1
  108. package/build/esm/ports/campaign-predicate.js +0 -2
  109. package/build/esm/ports/campaign-predicate.js.map +0 -1
  110. package/build/esm/ports/report-source.js +0 -7
  111. package/build/esm/ports/report-source.js.map +0 -1
  112. package/src/core/campaigns.ts +0 -1056
  113. package/src/core/ledger.ts +0 -242
  114. package/src/domain/report.ts +0 -203
  115. package/src/infrastructure/campaign-functions.ts +0 -98
  116. package/src/infrastructure/report-source-fake.ts +0 -10
  117. package/src/infrastructure/report-source-live.ts +0 -192
  118. package/src/ports/campaign-predicate.ts +0 -30
  119. package/src/ports/report-source.ts +0 -35
@@ -31,14 +31,21 @@ const isObject = (value: JsonValue): value is JsonObject =>
31
31
 
32
32
  const isList = (value: JsonValue): value is ReadonlyArray<JsonValue> => Array.isArray(value);
33
33
 
34
- // The generator names the recursive schemas after its own internal wrappers.
35
- // A stable name is what a `$ref` in an error message or a docs page can
36
- // point at: the tree's node, and a campaign's detector.
37
- const DEFINITION_NAMES: Readonly<Record<string, string>> = {
38
- Suspend_: "ManifestNode",
39
- Union_: "Detector",
34
+ // The generator names the recursive schemas after its own internal wrappers,
35
+ // numbered in the order it meets them. A stable name is what a `$ref` in an
36
+ // error message or a docs page can point at: the tree's node, and an
37
+ // objective's detector — told apart by what each declares, since the order
38
+ // moves with the manifest's shape.
39
+ const definitionNameOf = (name: string, definition: JsonValue): string => {
40
+ if (!/^(Suspend|Union)_\d*$/.test(name)) return name;
41
+ const text = JSON.stringify(definition);
42
+ if (text.includes('"children"')) return "ManifestNode";
43
+ if (text.includes('"all"')) return "Detector";
44
+ return name;
40
45
  };
41
46
 
47
+ const DEFINITION_NAMES: Record<string, string> = {};
48
+
42
49
  const USE_REFERENCE = "#/$defs/Use";
43
50
  const INCLUDE_REFERENCE = "#/$defs/Include";
44
51
 
@@ -98,8 +105,40 @@ const SCHEMA_PROPERTY: JsonObject = {
98
105
  description: "For editors. Ignored by the loader.",
99
106
  };
100
107
 
101
- export const manifestJsonSchema = (): JsonObject => {
102
- const generated = Schema.toJsonSchemaDocument(Manifest) as unknown as {
108
+ // A `Record` whose keys are checked — the campaign and objective ids —
109
+ // generates as `patternProperties`, which alone admits any other key beside
110
+ // the matching ones. Closed here, so an editor flags an id in the wrong
111
+ // shape as the loader would.
112
+ const withKeyPatterns = (value: JsonValue): JsonValue => {
113
+ if (Array.isArray(value)) return value.map(withKeyPatterns);
114
+ if (!isObject(value)) return value;
115
+ const rebuilt: Record<string, JsonValue> = {};
116
+ for (const [childKey, entry] of entriesOf(value)) rebuilt[childKey] = withKeyPatterns(entry);
117
+ if (
118
+ rebuilt.type === "object" &&
119
+ "patternProperties" in rebuilt &&
120
+ !("additionalProperties" in rebuilt)
121
+ ) {
122
+ rebuilt.additionalProperties = false;
123
+ }
124
+ return rebuilt;
125
+ };
126
+
127
+ export type ManifestJsonSchemaOptions = {
128
+ // The manifest keys of families the core does not own, as the fields of
129
+ // their own codecs — `CampaignsManifest.fields` from @goodbones/campaigns.
130
+ // Generated together with the core's, so the published schema describes the
131
+ // manifest a host with those families loaded will actually decode, and a
132
+ // host without them publishes a schema without those keys.
133
+ readonly extensions?: ReadonlyArray<Schema.Struct.Fields> | undefined;
134
+ };
135
+
136
+ export const manifestJsonSchema = (options: ManifestJsonSchemaOptions = {}): JsonObject => {
137
+ const whole =
138
+ options.extensions === undefined || options.extensions.length === 0
139
+ ? Manifest
140
+ : Schema.Struct(Object.assign({}, Manifest.fields, ...options.extensions));
141
+ const generated = Schema.toJsonSchemaDocument(whole) as unknown as {
103
142
  readonly schema: JsonObject;
104
143
  readonly definitions: JsonObject;
105
144
  };
@@ -108,6 +147,9 @@ export const manifestJsonSchema = (): JsonObject => {
108
147
  throw new Error("the manifest schema generated with no properties");
109
148
  }
110
149
 
150
+ for (const [name, definition] of entriesOf(generated.definitions)) {
151
+ DEFINITION_NAMES[name] = definitionNameOf(name, definition);
152
+ }
111
153
  const definitions: Record<string, JsonValue> = {};
112
154
  for (const [name, definition] of entriesOf(generated.definitions)) {
113
155
  definitions[DEFINITION_NAMES[name] ?? name] = admitReferences(definition);
@@ -124,7 +166,7 @@ export const manifestJsonSchema = (): JsonObject => {
124
166
  $schema: SCHEMA_PROPERTY,
125
167
  defs: DEFS_PROPERTY,
126
168
  ...Object.fromEntries(
127
- entriesOf(properties).map(([key, value]) => [key, admitReferences(value)]),
169
+ entriesOf(properties).map(([key, value]) => [key, withKeyPatterns(admitReferences(value))]),
128
170
  ),
129
171
  },
130
172
  $defs: { ...definitions, Use, Include },
@@ -135,8 +177,8 @@ export const manifestJsonSchema = (): JsonObject => {
135
177
  // `architecture.yaml` that the root manifest `include`s is shaped like. The
136
178
  // node's object form, with the `$schema` and `defs` keys such a file may carry
137
179
  // at the top, over the same definitions as the whole manifest.
138
- export const manifestNodeJsonSchema = (): JsonObject => {
139
- const whole = manifestJsonSchema();
180
+ export const manifestNodeJsonSchema = (options: ManifestJsonSchemaOptions = {}): JsonObject => {
181
+ const whole = manifestJsonSchema(options);
140
182
  const definitions = whole.$defs;
141
183
  if (definitions === undefined || !isObject(definitions)) {
142
184
  throw new Error("the manifest schema generated with no definitions");
@@ -2,14 +2,7 @@ import * as Result from "effect/Result";
2
2
  import * as Schema from "effect/Schema";
3
3
  import * as SchemaIssue from "effect/SchemaIssue";
4
4
 
5
- import {
6
- CampaignUnit,
7
- DeclarationKind,
8
- ImportProbeTarget,
9
- ProbeDiagnostic,
10
- ReportFormat,
11
- ResolveConfig,
12
- } from "../domain/architecture-config.js";
5
+ import { DeclarationKind, ResolveConfig } from "../domain/architecture-config.js";
13
6
  import { ConfigInvalid } from "../domain/architecture-error.js";
14
7
  import {
15
8
  type ManifestLocator,
@@ -17,6 +10,7 @@ import {
17
10
  renderManifestPath,
18
11
  } from "../domain/manifest-location.js";
19
12
  import { expandManifest, originOf, type Substitution } from "./expand.js";
13
+ import type { ManifestExtension } from "./extension.js";
20
14
 
21
15
  // A manifest is a tree of nodes keyed by path pattern, where everything the
22
16
  // architecture says about a part of the tree is written at that part of the tree.
@@ -112,7 +106,7 @@ const Members = Schema.Struct({
112
106
  // is about; exactly one demand says what is required of them, and none means
113
107
  // `forbid` — a selected site is the violation. Stated on a folder it covers
114
108
  // the subtree, like `members`.
115
- const SurfaceConvention = Schema.Union([
109
+ export const SurfaceConvention = Schema.Union([
116
110
  Schema.Literals(["kebab-case", "camelCase", "PascalCase", "snake_case"]),
117
111
  Schema.Struct({ regex: Schema.String }),
118
112
  ]);
@@ -281,201 +275,27 @@ const Limits = Schema.Struct({
281
275
  conformance: Schema.optionalKey(ConformanceCeilings),
282
276
  });
283
277
 
284
- // A campaign: a migration the repository is running, tracked as an object —
285
- // a detector, a rationale, a guide, an owner, a definition of done, and a
286
- // ledger of every place the pattern still occurs. Where a rule says what may
287
- // never happen, a campaign names what the code is moving away from.
288
- //
289
- // The detector is a predicate algebra: `all`, `any` and `not` over leaf
290
- // terms, the same three words ast-grep uses. Leaf terms reuse the other
291
- // families' vocabularies where one exists.
292
-
293
- // A regular expression over the whole repo-relative path, matched as the
294
- // `structure` naming rules are: with `subject` and `convention`, the capture
295
- // group named holds the name being judged, and the term holds when the name
296
- // has the convention's shape.
297
- const PathTerm = Schema.Struct({
298
- file: Globs,
299
- fileNot: Schema.optionalKey(Globs),
300
- subject: Schema.optionalKey(Schema.Finite),
301
- convention: Schema.optionalKey(SurfaceConvention),
302
- });
303
278
 
304
- // Holds when some import of the file resolves to the target — a path glob,
305
- // `{ external: <package> }` or `{ builtin: <module> }` — and, with `symbols`,
306
- // pulls one of those names across it.
307
- const ImportsTerm = Schema.Struct({
308
- resolves: ImportProbeTarget,
309
- symbols: Schema.optionalKey(Schema.Array(Schema.String)),
279
+ const decodeTree = Schema.decodeUnknownResult(Schema.Record(Schema.String, ManifestNodeSchema), {
280
+ errors: "all",
281
+ onExcessProperty: "error",
310
282
  });
311
283
 
312
- // Holds for an export site the selectors admit — the `surface` selectors.
313
- const ExportsTerm = Schema.Struct({
314
- name: Schema.optionalKey(Globs),
315
- kinds: Schema.optionalKey(Schema.Array(Schema.Literals(["named", "default", "namespace"]))),
316
- declares: Schema.optionalKey(Schema.Array(DeclarationKind)),
317
- reexport: Schema.optionalKey(Schema.Boolean),
318
- });
319
-
320
- // Holds for a member site the selectors admit — the `members` selectors.
321
- const MembersTerm = Schema.Struct({
322
- subject: Schema.Literals(["members", "calls"]),
323
- name: Schema.optionalKey(Globs),
324
- in: Schema.optionalKey(Globs),
325
- declares: Schema.optionalKey(Schema.Array(DeclarationKind)),
326
- });
327
-
328
- // A regular expression over the file's text, multiline.
329
- const ContentTerm = Schema.Struct({ regex: Schema.String });
330
-
331
- // How a metavariable is narrowed: by the text it captured, or by what the
332
- // identifier at its root is bound to — the module it resolves to and the
333
- // name it was imported as (`member`), which is how `class $N extends $BASE`
334
- // says "a React component" rather than "any class with a base".
335
- const CaptureNarrowing = Schema.Struct({
336
- regex: Schema.optionalKey(Schema.String),
337
- binding: Schema.optionalKey(
338
- Schema.Struct({
339
- resolves: ImportProbeTarget,
340
- member: Schema.optionalKey(Schema.Array(Schema.String)),
341
- }),
342
- ),
343
- });
344
-
345
- // An ast-grep rule object — `pattern`, `kind`, `regex`, `has`, `inside`,
346
- // `precedes`, `follows`, `nthChild`, `all`, `any`, `not` — with `where`
347
- // beside it. The rule is the engine's to validate; the manifest only knows
348
- // which keys are a rule's and which is the narrowing. `kind` names are the
349
- // engine's node kinds, which today are tree-sitter's.
350
- const SyntaxTerm = Schema.Struct({
351
- pattern: Schema.optionalKey(Schema.Unknown),
352
- kind: Schema.optionalKey(Schema.Unknown),
353
- regex: Schema.optionalKey(Schema.Unknown),
354
- nthChild: Schema.optionalKey(Schema.Unknown),
355
- inside: Schema.optionalKey(Schema.Unknown),
356
- has: Schema.optionalKey(Schema.Unknown),
357
- precedes: Schema.optionalKey(Schema.Unknown),
358
- follows: Schema.optionalKey(Schema.Unknown),
359
- all: Schema.optionalKey(Schema.Unknown),
360
- any: Schema.optionalKey(Schema.Unknown),
361
- not: Schema.optionalKey(Schema.Unknown),
362
- where: Schema.optionalKey(Schema.Record(Schema.String, CaptureNarrowing)),
363
- });
364
-
365
- // One program to run or file to read, or several: a tool that takes one
366
- // project at a time is run once per project, and the outputs are read as
367
- // one report — a diagnostic two of them print is one diagnostic.
368
- const ReportSources = Schema.Union([
369
- Schema.String,
370
- Schema.Array(Schema.String).check(Schema.isMinLength(1)),
371
- ]);
372
-
373
- // A finding of another program, read from the output of `command` (run
374
- // from the repository root, once per `check`) or from `file` (written by
375
- // an earlier step) in one of the known formats — `tsc`, `eslint --format
376
- // json`, `oxlint --format json` — or by a `regex` with named groups. Holds
377
- // for each diagnostic on the file whose code the term speaks to.
378
- const ReportTerm = Schema.Struct({
379
- command: Schema.optionalKey(ReportSources),
380
- file: Schema.optionalKey(ReportSources),
381
- format: ReportFormat,
382
- pattern: Schema.optionalKey(Schema.String),
383
- codes: Schema.optionalKey(Schema.Array(Schema.String)),
384
- codesNot: Schema.optionalKey(Schema.Array(Schema.String)),
385
- }).check(
386
- // Refused at decode, where the issue names a line, rather than in the
387
- // lowering: a term that names both sources or neither, and a `regex`
388
- // term with nothing to match lines against.
389
- Schema.makeFilter((term) => {
390
- const issues: Array<Schema.FilterIssue> = [];
391
- if ((term.command === undefined) === (term.file === undefined)) {
392
- issues.push(
393
- "a report term names exactly one of `command` (a program to run) and `file` (a report already written)",
394
- );
395
- }
396
- if (term.format === "regex" && term.pattern === undefined) {
397
- issues.push({
398
- path: ["pattern"],
399
- issue: "a `regex` report term needs a `pattern` with named groups `file` and `line`",
400
- });
401
- }
402
- return issues;
403
- }),
404
- );
405
-
406
- export type DetectorSpec =
407
- | { readonly all: ReadonlyArray<DetectorSpec> }
408
- | { readonly any: ReadonlyArray<DetectorSpec> }
409
- | { readonly not: DetectorSpec }
410
- | { readonly path: typeof PathTerm.Type }
411
- | { readonly imports: typeof ImportsTerm.Type }
412
- | { readonly exports: typeof ExportsTerm.Type }
413
- | { readonly members: typeof MembersTerm.Type }
414
- // The `structure` parity strings: holds when every named sibling exists.
415
- | { readonly requires: ReadonlyArray<string> }
416
- | { readonly content: typeof ContentTerm.Type }
417
- | { readonly syntax: typeof SyntaxTerm.Type }
418
- | { readonly report: typeof ReportTerm.Type }
419
- // `module#export`: a predicate function the host imports before loading.
420
- | { readonly fn: string };
421
-
422
- // Each object carries exactly one term key, so a misspelled one is a decode
423
- // error that names the line rather than a term quietly dropped.
424
- const DetectorRef = Schema.suspend((): Schema.Codec<DetectorSpec> => DetectorSpec);
425
-
426
- const DetectorSpec = Schema.Union([
427
- Schema.Struct({ all: Schema.Array(DetectorRef) }),
428
- Schema.Struct({ any: Schema.Array(DetectorRef) }),
429
- Schema.Struct({ not: DetectorRef }),
430
- Schema.Struct({ path: PathTerm }),
431
- Schema.Struct({ imports: ImportsTerm }),
432
- Schema.Struct({ exports: ExportsTerm }),
433
- Schema.Struct({ members: MembersTerm }),
434
- Schema.Struct({ requires: Schema.Array(Schema.String) }),
435
- Schema.Struct({ content: ContentTerm }),
436
- Schema.Struct({ syntax: SyntaxTerm }),
437
- Schema.Struct({ report: ReportTerm }),
438
- Schema.Struct({ fn: Schema.String }),
439
- ]);
440
-
441
- // A source the campaign is proven against at load. `path` alone proves a
442
- // path-shaped detector; `source` is parsed, `edges` answers the `imports`
443
- // term and a binding narrowing in place of the live resolver, `files`
444
- // answers `requires` in place of the file system, and `report` answers a
445
- // `report` term in place of running anything — one-based positions, as a
446
- // tool prints them.
447
- const CampaignProbe = Schema.Struct({
448
- path: Schema.String,
449
- source: Schema.optionalKey(Schema.String),
450
- edges: Schema.optionalKey(Schema.Record(Schema.String, ImportProbeTarget)),
451
- files: Schema.optionalKey(Schema.Array(Schema.String)),
452
- report: Schema.optionalKey(Schema.Array(ProbeDiagnostic)),
453
- });
284
+ // A tree of nodes, as an `endState` is once rebased: decoded like the
285
+ // manifest's own, every issue listed.
286
+ export const decodeManifestTree = (
287
+ raw: unknown,
288
+ ): Result.Result<Readonly<Record<string, ManifestNode>>, string> => {
289
+ const decoded = decodeTree(raw);
290
+ return Result.isFailure(decoded)
291
+ ? Result.fail(
292
+ flatten(decoded.failure.issue)
293
+ .issues.map((issue) => `${renderManifestPath(pathOf(issue))}: ${issue.message}`)
294
+ .join("\n"),
295
+ )
296
+ : Result.succeed(decoded.success);
297
+ };
454
298
 
455
- // `30d`, `12h`: how long a campaign may go without progress before the
456
- // conformance report calls it stalled.
457
- const Duration = Schema.String.check(Schema.isPattern(/^\d+[dh]$/));
458
-
459
- const Campaign = Schema.Struct({
460
- // The ledger file's name, and the rule name's tail: `campaign/<id>`.
461
- id: Schema.String.check(Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)),
462
- title: Schema.optionalKey(Schema.String),
463
- why: Schema.String,
464
- // What a reader at a hit does about it — the message every hit carries.
465
- how: Schema.String,
466
- owner: Schema.optionalKey(Schema.String),
467
- // Which files the campaign selects. Alias-aware globs, as the graph rules
468
- // take; a campaign's reach is its scope, so it joins no coverage row.
469
- scope: Globs,
470
- unit: CampaignUnit,
471
- detect: DetectorSpec,
472
- probes: Schema.Struct({
473
- fires: Schema.Array(CampaignProbe),
474
- ignores: Schema.optionalKey(Schema.Array(CampaignProbe)),
475
- }),
476
- staleAfter: Duration,
477
- onComplete: Schema.optionalKey(Schema.Literals(["keep", "remove"])),
478
- });
479
299
 
480
300
  export const Manifest = Schema.Struct({
481
301
  // How an import specifier becomes a file. Every pattern below is matched
@@ -491,10 +311,6 @@ export const Manifest = Schema.Struct({
491
311
  exports: Schema.optionalKey(Schema.Array(ExportRestriction)),
492
312
  graph: Schema.optionalKey(Graph),
493
313
  limits: Schema.optionalKey(Limits),
494
- campaigns: Schema.optionalKey(Schema.Array(Campaign)),
495
- // Where each campaign's ledger is written: `<ledger>/<id>.json`, relative
496
- // to the manifest. Defaults to `.architecture-campaigns`.
497
- ledger: Schema.optionalKey(Schema.String),
498
314
  // Shorthands expanded in every glob, so a pattern reads the way the repo's own
499
315
  // imports do rather than repeating `packages/server/src` on every line.
500
316
  aliases: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
@@ -510,17 +326,8 @@ export type GraphSpec = typeof Graph.Type;
510
326
  export type LimitsSpec = typeof Limits.Type;
511
327
  export type NamingSpec = typeof Naming.Type;
512
328
  export type ExportRestriction = typeof ExportRestriction.Type;
513
- export type CampaignSpec = typeof Campaign.Type;
514
- export type CampaignProbeSpec = typeof CampaignProbe.Type;
515
- export type SyntaxTermSpec = typeof SyntaxTerm.Type;
516
329
 
517
- export const DEFAULT_LEDGER_DIR = ".architecture-campaigns";
518
330
 
519
- // `30d` → milliseconds. The schema has already refused any other shape.
520
- export const durationMs = (duration: string): number => {
521
- const amount = Number(duration.slice(0, -1));
522
- return amount * (duration.endsWith("h") ? 3_600_000 : 86_400_000);
523
- };
524
331
 
525
332
  export const globsOf = (globs: string | ReadonlyArray<string>): ReadonlyArray<string> =>
526
333
  typeof globs === "string" ? [globs] : globs;
@@ -534,6 +341,10 @@ const flatten = SchemaIssue.makeFormatterStandardSchemaV1();
534
341
 
535
342
  export type DecodedManifest = {
536
343
  readonly manifest: Manifest;
344
+ // What each family the core does not own decoded out of the keys it claims,
345
+ // keyed by the extension's id. The core carries these and never looks
346
+ // inside them.
347
+ readonly extensions: ReadonlyMap<string, unknown>;
537
348
  // Things the manifest said in a form that still loads but is on its way out.
538
349
  // The host prints them; nothing else acts on them.
539
350
  readonly notices: ReadonlyArray<string>;
@@ -639,10 +450,15 @@ const normalizeLegacyMembers = (
639
450
  return { input: { ...input, tree }, notices };
640
451
  };
641
452
 
453
+
642
454
  export type DecodeManifestOptions = {
643
455
  // Turns a path in the file into a line and column. The YAML reader supplies
644
456
  // one; a JavaScript module has no positions to give and passes nothing.
645
457
  readonly locate?: ManifestLocator | undefined;
458
+ // Families the core does not own. Each claims top-level keys, which are
459
+ // split off before the core decodes what is left and handed to that
460
+ // family's own codec.
461
+ readonly extensions?: ReadonlyArray<ManifestExtension> | undefined;
646
462
  };
647
463
 
648
464
  const fileLabelOf = (configPath: string): string => configPath.split(/[\\/]/).at(-1) ?? configPath;
@@ -714,7 +530,35 @@ export const decodeManifest = (
714
530
  }
715
531
  const { substitutions, value } = expanded.success;
716
532
 
717
- const resolve = normalizeLegacyResolve(value);
533
+ const extensions = options.extensions ?? [];
534
+ const describe = (path: ManifestPath, detail: string): string =>
535
+ describeIssue(configPath, options.locate, substitutions, path, detail);
536
+
537
+ // The keys a family the core does not own claims are split off here. What
538
+ // is left is decoded by the core's codec with excess properties refused, so
539
+ // a key nobody claims is still the misspelling it always was.
540
+ const claimed = new Map<string, string>();
541
+ for (const extension of extensions) {
542
+ for (const key of extension.manifestKeys) {
543
+ const already = claimed.get(key);
544
+ if (already !== undefined) {
545
+ return Result.fail(
546
+ new ConfigInvalid({
547
+ configPath,
548
+ detail:
549
+ `two loaded families both claim the manifest key \`${key}\` ` +
550
+ `("${already}" and "${extension.id}"). Load one of them.`,
551
+ }),
552
+ );
553
+ }
554
+ claimed.set(key, extension.id);
555
+ }
556
+ }
557
+ const core = isRecord(value)
558
+ ? Object.fromEntries(Object.entries(value).filter(([key]) => !claimed.has(key)))
559
+ : value;
560
+
561
+ const resolve = normalizeLegacyResolve(core);
718
562
  const members = normalizeLegacyMembers(resolve.input);
719
563
  const decoded = decode(members.input);
720
564
  if (Result.isFailure(decoded)) {
@@ -728,8 +572,32 @@ export const decodeManifest = (
728
572
  }),
729
573
  );
730
574
  }
575
+
576
+ // Each family decodes its own slice, and renders its issues the way the
577
+ // core renders its own. One family's failure is reported on its own: the
578
+ // core's decode already succeeded, so there is nothing to collect it with.
579
+ const decodedExtensions = new Map<string, unknown>();
580
+ for (const extension of extensions) {
581
+ const slice = isRecord(value)
582
+ ? Object.fromEntries(
583
+ extension.manifestKeys.flatMap((key) => (key in value ? [[key, value[key]]] : [])),
584
+ )
585
+ : {};
586
+ const result = extension.decode(slice, describe);
587
+ if (Result.isFailure(result)) {
588
+ return Result.fail(
589
+ new ConfigInvalid({
590
+ configPath,
591
+ detail: `the manifest does not decode:\n${result.failure.join("\n")}`,
592
+ }),
593
+ );
594
+ }
595
+ decodedExtensions.set(extension.id, result.success);
596
+ }
597
+
731
598
  return Result.succeed({
732
599
  manifest: decoded.success,
600
+ extensions: decodedExtensions,
733
601
  notices: [...resolve.notices, ...members.notices],
734
602
  substitutions,
735
603
  });
@@ -5,4 +5,7 @@ export type FileSystem = {
5
5
  readonly exists: (repoRelativePath: string) => boolean;
6
6
  // The file's text, or `null` when it is absent or unreadable.
7
7
  readonly readText: (repoRelativePath: string) => string | null;
8
+ // The names in a folder — the per-sector records under a campaign's
9
+ // ledger directory are found this way. Empty when the folder is absent.
10
+ readonly list: (repoRelativeDir: string) => ReadonlyArray<string>;
8
11
  };
package/src/testing.ts CHANGED
@@ -4,5 +4,4 @@
4
4
  export { makeFactExtractorFake } from "./infrastructure/fact-extractor-fake.js";
5
5
  export { makeFileSystemFake } from "./infrastructure/file-system-fake.js";
6
6
  export { makeModuleResolverFake } from "./infrastructure/module-resolver-fake.js";
7
- export { makeReportSourceFake } from "./infrastructure/report-source-fake.js";
8
7
  export { makeSyntaxMatcherFake, type StagedMatch } from "./infrastructure/syntax-matcher-fake.js";
@@ -1,141 +0,0 @@
1
- import * as Result from "effect/Result";
2
- import type { BindingKind, CampaignProbe, CampaignRule, CampaignUnit, DeclarationKind, MemberSubject, ReportFormat } from "../domain/architecture-config.js";
3
- import { PatternInvalid } from "../domain/architecture-error.js";
4
- import type { SourceFacts } from "../domain/facts.js";
5
- import type { Violation } from "../domain/violation.js";
6
- import type { CampaignPredicate, Range } from "../ports/campaign-predicate.js";
7
- import type { FactExtractor } from "../ports/fact-extractor.js";
8
- import type { FileSystem } from "../ports/file-system.js";
9
- import type { ModuleResolver } from "../ports/module-resolver.js";
10
- import type { ReportSource, ReportSpec } from "../ports/report-source.js";
11
- import type { SyntaxMatcher, SyntaxTree } from "../ports/syntax-matcher.js";
12
- export type { CampaignPredicate, CampaignPredicateInput, CampaignSubject, Range, } from "../ports/campaign-predicate.js";
13
- type Target = {
14
- readonly kind: "path";
15
- readonly pattern: RegExp;
16
- } | {
17
- readonly kind: "external";
18
- readonly name: string;
19
- } | {
20
- readonly kind: "builtin";
21
- readonly name: string;
22
- };
23
- type CompiledNarrowing = {
24
- readonly capture: string;
25
- readonly regex: RegExp | null;
26
- readonly binding: {
27
- readonly resolves: Target;
28
- readonly member: ReadonlyArray<string> | null;
29
- } | null;
30
- };
31
- export type CompiledDetector = {
32
- readonly kind: "all";
33
- readonly terms: ReadonlyArray<CompiledDetector>;
34
- } | {
35
- readonly kind: "any";
36
- readonly terms: ReadonlyArray<CompiledDetector>;
37
- } | {
38
- readonly kind: "not";
39
- readonly term: CompiledDetector;
40
- } | {
41
- readonly kind: "path";
42
- readonly file: ReadonlyArray<RegExp>;
43
- readonly fileNot: ReadonlyArray<RegExp>;
44
- readonly subject: number | null;
45
- readonly convention: RegExp | null;
46
- } | {
47
- readonly kind: "imports";
48
- readonly resolves: Target;
49
- readonly symbols: ReadonlyArray<string> | null;
50
- } | {
51
- readonly kind: "exports";
52
- readonly name: ReadonlyArray<RegExp>;
53
- readonly kinds: ReadonlyArray<BindingKind> | null;
54
- readonly declares: ReadonlyArray<DeclarationKind> | null;
55
- readonly reexport: boolean | null;
56
- } | {
57
- readonly kind: "members";
58
- readonly subject: MemberSubject;
59
- readonly name: ReadonlyArray<RegExp>;
60
- readonly in: ReadonlyArray<RegExp>;
61
- readonly declares: ReadonlyArray<DeclarationKind> | null;
62
- } | {
63
- readonly kind: "requires";
64
- readonly templates: ReadonlyArray<string>;
65
- } | {
66
- readonly kind: "content";
67
- readonly regex: RegExp;
68
- } | {
69
- readonly kind: "syntax";
70
- readonly rule: unknown;
71
- readonly where: ReadonlyArray<CompiledNarrowing>;
72
- } | {
73
- readonly kind: "report";
74
- readonly command: ReadonlyArray<string> | null;
75
- readonly file: ReadonlyArray<string> | null;
76
- readonly format: ReportFormat;
77
- readonly pattern: string | null;
78
- readonly codes: ReadonlySet<string> | null;
79
- readonly codesNot: ReadonlySet<string>;
80
- } | {
81
- readonly kind: "fn";
82
- readonly name: string;
83
- };
84
- export type CompiledCampaign = {
85
- readonly name: string;
86
- readonly id: string;
87
- readonly title: string | null;
88
- readonly message: string;
89
- readonly why: string;
90
- readonly owner: string | null;
91
- readonly scope: ReadonlyArray<RegExp>;
92
- readonly unit: CampaignUnit;
93
- readonly detect: CompiledDetector;
94
- readonly probes: {
95
- readonly fires: ReadonlyArray<CampaignProbe>;
96
- readonly ignores: ReadonlyArray<CampaignProbe>;
97
- };
98
- readonly staleAfter: number;
99
- readonly onComplete: "keep" | "remove";
100
- };
101
- export declare const compileCampaignRule: (rule: CampaignRule) => Result.Result<CompiledCampaign, PatternInvalid>;
102
- export declare const compileCampaignRules: (rules: ReadonlyArray<CampaignRule>) => Result.Result<ReadonlyArray<CompiledCampaign>, PatternInvalid>;
103
- export declare const campaignsSelecting: (rules: ReadonlyArray<CompiledCampaign>, file: string) => ReadonlyArray<CompiledCampaign>;
104
- declare const LEAF_ORDER: readonly ["path", "imports", "exports", "members", "requires", "content", "syntax", "report", "fn"];
105
- type LeafKind = (typeof LEAF_ORDER)[number];
106
- export declare const leafTermsOf: (detector: CompiledDetector) => ReadonlyArray<LeafKind>;
107
- export type CampaignInput = {
108
- readonly file: string;
109
- readonly text: string;
110
- readonly facts: SourceFacts;
111
- readonly resolver: ModuleResolver;
112
- readonly fileSystem: FileSystem;
113
- readonly syntax: SyntaxTree | null;
114
- readonly functions: ReadonlyMap<string, CampaignPredicate>;
115
- readonly reports: ReportSource;
116
- };
117
- export type CampaignHit = {
118
- readonly violation: Violation;
119
- readonly campaign: string;
120
- readonly range?: Range;
121
- };
122
- export declare const matchKeyOf: (anchor: string | null, text: string) => string;
123
- export declare const evaluateCampaign: (rule: CompiledCampaign, input: CampaignInput) => ReadonlyArray<CampaignHit>;
124
- export declare const reportSpecsOf: (rules: ReadonlyArray<CompiledCampaign>) => ReadonlyArray<ReportSpec>;
125
- export declare const evaluateCampaigns: (selected: ReadonlyArray<CompiledCampaign>, input: CampaignInput) => ReadonlyArray<CampaignHit>;
126
- export type TermAnswer = {
127
- readonly term: string;
128
- readonly answer: boolean;
129
- readonly count?: number;
130
- };
131
- export declare const explainCampaign: (rule: CompiledCampaign, input: CampaignInput) => ReadonlyArray<TermAnswer>;
132
- export declare const probeInputOf: (probe: CampaignProbe, extractor: FactExtractor, matcher: SyntaxMatcher | null, functions: ReadonlyMap<string, CampaignPredicate>) => CampaignInput;
133
- export type FailedProbe = {
134
- readonly name: string;
135
- readonly probe: CampaignProbe;
136
- readonly expected: "fires" | "ignores";
137
- readonly admittedBy?: string;
138
- readonly outOfScope?: boolean;
139
- };
140
- export declare const campaignsFailingTheirProbe: (rules: ReadonlyArray<CompiledCampaign>, extractor: FactExtractor, matcherFor: (file: string) => SyntaxMatcher | null, functions: ReadonlyMap<string, CampaignPredicate>) => ReadonlyArray<FailedProbe>;
141
- //# sourceMappingURL=campaigns.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"campaigns.d.ts","sourceRoot":"","sources":["../../../src/core/campaigns.ts"],"names":[],"mappings":"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"}