@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
@@ -18,7 +18,7 @@ import type { ViolationKind } from "./violation.js";
18
18
  export const SNAPSHOT_SCHEMA_ID =
19
19
  "https://dataquail.github.io/goodbones/schema/conformance.schema.json";
20
20
 
21
- export const SNAPSHOT_VERSION = 1;
21
+ export const SNAPSHOT_VERSION = 2;
22
22
 
23
23
  const describe = <S extends Schema.Top>(schema: S, description: string) =>
24
24
  schema.annotate({ description });
@@ -82,7 +82,16 @@ export const SnapshotViolation = Schema.Struct({
82
82
  baselined: describe(Schema.Boolean, "Carried by the baseline, so `check` does not fail on it."),
83
83
  ledgered: describe(
84
84
  Schema.Boolean,
85
- "For a campaign hit: carried by the campaign's ledger, so `check` does not fail on it.",
85
+ "For a campaign hit: carried by the objective's ledger, so `check` does not fail on it.",
86
+ ),
87
+ objective: Schema.optionalKey(
88
+ describe(Schema.String, "For a campaign hit: the objective that fired."),
89
+ ),
90
+ sector: Schema.optionalKey(
91
+ describe(Schema.String, "For a campaign hit: the sector the hit falls in."),
92
+ ),
93
+ entry: Schema.optionalKey(
94
+ describe(Schema.String, "For a campaign hit: its ledger entry, relative to the sector's root."),
86
95
  ),
87
96
  });
88
97
 
@@ -130,46 +139,134 @@ export const SnapshotVacancy = Schema.Struct({
130
139
  ),
131
140
  });
132
141
 
133
- // One campaign's burn-down: what its ledger says, and what the clock says
134
- // about it. Every count is over ledger entries, which are hits by fingerprint.
135
- export const SnapshotCampaign = Schema.Struct({
136
- id: describe(Schema.String, "The campaign's id; its ledger is `<ledger>/<id>.json`."),
137
- title: Schema.optionalKey(describe(Schema.String, "The campaign's title, when it states one.")),
138
- owner: Schema.optionalKey(
139
- describe(Schema.String, "Who is running the campaign, as the manifest names them."),
142
+ // One objective's burn-down: what its ledger says, summed over its sectors.
143
+ export const SnapshotObjective = Schema.Struct({
144
+ id: describe(
145
+ Schema.String,
146
+ "The objective's id; its ledger is `<ledger>/<campaign>/<objective>.json`.",
147
+ ),
148
+ phase: describe(
149
+ Schema.NullOr(Schema.String),
150
+ "The phase naming the objective, or `null` for one no phase names — in window everywhere.",
140
151
  ),
141
- initial: describe(Schema.Finite, "Entries the day the ledger was written."),
152
+ initial: describe(Schema.Finite, "Holdouts recorded as each sector entered the window, summed."),
142
153
  allowed: describe(
143
154
  Schema.Finite,
144
- "Entries added since by `campaigns allow`, each with a regression record.",
155
+ "Holdouts added since by `objectives concede` or a re-baseline, each with a concession.",
145
156
  ),
146
- count: describe(Schema.Finite, "Entries in the ledger now."),
147
- fixed: describe(
157
+ count: describe(Schema.Finite, "Holdouts in the ledger now, every sector summed."),
158
+ cleared: describe(
148
159
  Schema.Finite,
149
- "Entries removed by `campaigns prune` since the ledger was written.",
160
+ "Holdouts removed by `objectives clear` because they stopped firing.",
161
+ ),
162
+ closed: describe(
163
+ Schema.Finite,
164
+ "Holdouts still firing when a sector left the window — not progress.",
150
165
  ),
151
166
  progress: describe(
152
167
  Schema.Finite,
153
- "`1 - count / (initial + allowed)`: how much of everything ever ledgered has been paid down.",
168
+ "`1 - count / (initial + allowed - closed)`: how much of everything ever ledgered has been paid down.",
154
169
  ),
155
- lastProgress: describe(
156
- Schema.String,
157
- "When an entry last left the ledger, ISO 8601 — the stall clock's reading.",
170
+ lastCleared: describe(
171
+ Schema.NullOr(Schema.String),
172
+ "When a holdout last left the ledger, ISO 8601 — the stall clock's reading; `null` with no ledger.",
173
+ ),
174
+ concessions: describe(Schema.Finite, "How many times a count was allowed to go up."),
175
+ complete: describe(Schema.Boolean, "No holdouts remain."),
176
+ ledgered: describe(
177
+ Schema.Boolean,
178
+ "Whether a ledger exists. An objective with none has been declared and not yet cleared.",
179
+ ),
180
+ });
181
+
182
+ // One phase of the ladder, and how many sectors stand at it.
183
+ export const SnapshotPhase = Schema.Struct({
184
+ id: describe(Schema.String, "The phase's id."),
185
+ defined: describe(
186
+ Schema.Boolean,
187
+ "Whether the phase names criteria. An open phase has only an intent, and is last.",
188
+ ),
189
+ sectors: describe(Schema.Finite, "How many sectors are derived to stand at this phase."),
190
+ });
191
+
192
+ // One sector: where it stands, and its residue toward its next phase.
193
+ export const SnapshotSector = Schema.Struct({
194
+ name: describe(Schema.String, "The sector's identity, as its perimeter names it."),
195
+ phase: describe(
196
+ Schema.NullOr(Schema.String),
197
+ "The phase the sector is derived to stand at, or `null` when it is past the last one.",
198
+ ),
199
+ reached: describe(
200
+ Schema.NullOr(Schema.String),
201
+ "The furthest phase the sector's record says it has reached; `null` before its first `clear`.",
202
+ ),
203
+ files: describe(Schema.Finite, "How many files the sector claims."),
204
+ residue: describe(
205
+ Schema.Record(Schema.String, Schema.Finite),
206
+ "One dimension per objective in window for the sector: its holdouts there, never summed.",
207
+ ),
208
+ stalled: describe(
209
+ Schema.Boolean,
210
+ "Holdouts remain for the sector and nothing has been cleared, attested or noted within `staleAfter`.",
211
+ ),
212
+ });
213
+
214
+ // One campaign: its objectives' burn-down, the distribution of its sectors
215
+ // over its phases, the legacy remainder, and what changed in its plan.
216
+ export const SnapshotCampaign = Schema.Struct({
217
+ id: describe(Schema.String, "The campaign's id; its ledgers are under `<ledger>/<id>/`."),
218
+ title: Schema.optionalKey(describe(Schema.String, "The campaign's title, when it states one.")),
219
+ owner: Schema.optionalKey(
220
+ describe(Schema.String, "Who is running the campaign, as the manifest names them."),
221
+ ),
222
+ count: describe(Schema.Finite, "Holdouts across every objective and sector."),
223
+ progress: describe(
224
+ Schema.Finite,
225
+ "Cleared over everything ever ledgered, across the campaign's objectives.",
226
+ ),
227
+ objectives: describe(Schema.Array(SnapshotObjective), "Every objective, in manifest order."),
228
+ phases: describe(
229
+ Schema.Array(SnapshotPhase),
230
+ "The ladder, in order, with how many sectors stand at each phase.",
231
+ ),
232
+ sectors: describe(Schema.Array(SnapshotSector), "Every sector the perimeter births, by name."),
233
+ legacy: describe(
234
+ Schema.Struct({
235
+ files: describe(Schema.Finite, "Files in the scope no sector claims."),
236
+ holdouts: describe(
237
+ Schema.Finite,
238
+ "Holdouts in the legacy, under the first phase's objectives — where a holdout moved out of a sector lands.",
239
+ ),
240
+ }),
241
+ "The unclaimed remainder of the scope, which stands at the first phase and is not a sink.",
242
+ ),
243
+ plan: describe(
244
+ Schema.Struct({
245
+ refined: describe(
246
+ Schema.Array(Schema.String),
247
+ "Phases refined since the last `clear`: an open phase that gained criteria, or a new one. Free.",
248
+ ),
249
+ changed: describe(
250
+ Schema.Array(Schema.String),
251
+ "Defined phases whose definition changed since the last `clear` — what a reviewer reads every time.",
252
+ ),
253
+ unreceipted: describe(
254
+ Schema.Array(Schema.String),
255
+ "Changed phases carrying no new concession; `check` fails on them.",
256
+ ),
257
+ }),
258
+ "What changed in the plan, refinements and changes apart.",
158
259
  ),
159
- regressions: describe(Schema.Finite, "How many times the count was allowed to go up."),
160
260
  stalled: describe(
161
261
  Schema.Boolean,
162
- "Entries remain and `lastProgress` is older than the campaign's `staleAfter`.",
262
+ "Holdouts remain and nothing has been cleared, attested or noted within the campaign's `staleAfter`.",
163
263
  ),
164
- complete: describe(Schema.Boolean, "No entries remain."),
264
+ complete: describe(Schema.Boolean, "No holdouts remain in any objective."),
165
265
  onComplete: describe(
166
266
  Schema.Literals(["keep", "remove"]),
167
267
  "What the manifest asks once complete: keep the campaign as a guard, or remove it.",
168
268
  ),
169
- ledgered: describe(
170
- Schema.Boolean,
171
- "Whether a ledger exists. A campaign with none has been declared and not yet initialised.",
172
- ),
269
+ ledgered: describe(Schema.Boolean, "Whether every objective has a ledger."),
173
270
  });
174
271
 
175
272
  export const Snapshot = Schema.Struct({
@@ -271,6 +368,9 @@ export type SnapshotSlack = typeof SnapshotSlack.Type;
271
368
  export type SnapshotConcentration = typeof SnapshotConcentration.Type;
272
369
  export type SnapshotVacancy = typeof SnapshotVacancy.Type;
273
370
  export type SnapshotCampaign = typeof SnapshotCampaign.Type;
371
+ export type SnapshotObjective = typeof SnapshotObjective.Type;
372
+ export type SnapshotPhase = typeof SnapshotPhase.Type;
373
+ export type SnapshotSector = typeof SnapshotSector.Type;
274
374
 
275
375
  // Decodes a document some other run wrote — the base of a pull request, a
276
376
  // stored one — refusing a key the shape does not declare, so a consumer never
package/src/index.ts CHANGED
@@ -3,6 +3,15 @@
3
3
  // manifest into a policy. A host composes these with a language pack and the
4
4
  // live file system; a language pack implements the ports. Nothing here names a
5
5
  // language. The fakes are under `@goodbones/core/testing`.
6
+ //
7
+ // A family the core does not own — `campaigns`, in @goodbones/campaigns —
8
+ // arrives as a `PolicyExtension` the host passes to `loadPolicy`: it claims
9
+ // its own manifest keys, decodes them with its own codec, and reads its state
10
+ // back off `LoadedPolicy.extensions`. What such a family needs from here is
11
+ // exported deliberately: the glob primitives its lowering must match
12
+ // character for character, the pattern vocabulary its detectors share with
13
+ // the other families, and `lowerManifest` itself, which an `endState` runs a
14
+ // sector-relative tree through.
6
15
  export {
7
16
  type Baseline,
8
17
  type BaselineFilter,
@@ -14,25 +23,6 @@ export {
14
23
  staleEntriesOf,
15
24
  unbaselined,
16
25
  } from "./core/baseline.js";
17
- export {
18
- type CampaignHit,
19
- type CampaignInput,
20
- campaignsFailingTheirProbe,
21
- campaignsSelecting,
22
- compileCampaignRule,
23
- compileCampaignRules,
24
- type CompiledCampaign,
25
- type CompiledDetector,
26
- evaluateCampaign,
27
- evaluateCampaigns,
28
- explainCampaign,
29
- type FailedProbe,
30
- leafTermsOf,
31
- matchKeyOf,
32
- probeInputOf,
33
- reportSpecsOf,
34
- type TermAnswer,
35
- } from "./core/campaigns.js";
36
26
  export {
37
27
  type Coverage,
38
28
  type CoverageFamily,
@@ -83,27 +73,6 @@ export {
83
73
  rulesSelecting,
84
74
  type SelectedRule,
85
75
  } from "./core/imports.js";
86
- export {
87
- allowed,
88
- decodeLedger,
89
- EMPTY_LEDGER,
90
- entryOf,
91
- isComplete,
92
- isStalled,
93
- type Ledger,
94
- ledgerArithmeticHolds,
95
- ledgerOf,
96
- Ledger as LedgerSchema,
97
- newEntriesOf,
98
- progressOf,
99
- pruned,
100
- reconcile,
101
- type Reconciliation,
102
- type Regression,
103
- type RegressionRecord,
104
- serializeLedger,
105
- staleEntriesOf as staleLedgerEntriesOf,
106
- } from "./core/ledger.js";
107
76
  export {
108
77
  type CompiledMemberRule,
109
78
  compileMemberRules,
@@ -111,6 +80,16 @@ export {
111
80
  memberRulesFailingTheirProbe,
112
81
  memberRulesSelecting,
113
82
  } from "./core/members.js";
83
+ export {
84
+ compilePatterns,
85
+ firstFromMatch,
86
+ matchesAny,
87
+ type Selectable,
88
+ sourcesOf,
89
+ targetAllowed,
90
+ type Targeted,
91
+ validateTargetPatterns,
92
+ } from "./core/patterns.js";
114
93
  export {
115
94
  type Concentration,
116
95
  type ObservedEdge,
@@ -124,6 +103,7 @@ export {
124
103
  EMPTY_STRUCTURE,
125
104
  evaluateStructure,
126
105
  requiredSiblingsOf,
106
+ siblingsOf,
127
107
  structureRulesFailingTheirProbe,
128
108
  } from "./core/structure.js";
129
109
  export {
@@ -135,12 +115,8 @@ export {
135
115
  } from "./core/surface.js";
136
116
  export {
137
117
  type Allowance,
138
- type BindingKind,
139
- type CampaignProbe,
140
- type CampaignRule,
141
- type CampaignUnit,
142
- type DeclarationKind,
143
- type Detector,
118
+ BindingKind,
119
+ DeclarationKind,
144
120
  type ExportFix,
145
121
  type ExportRule,
146
122
  type GraphConfig,
@@ -148,11 +124,11 @@ export {
148
124
  type GraphOrphanRule,
149
125
  type GraphReachRule,
150
126
  type ImportProbe,
151
- type ImportProbeTarget,
127
+ ImportProbeTarget,
152
128
  type ImportRule,
153
129
  type MemberRule,
154
- type MemberSubject,
155
- type ProbeDiagnostic,
130
+ MemberSubject,
131
+ PatternList,
156
132
  type ResolveConfig,
157
133
  type ResolveScope,
158
134
  type StructureConfig,
@@ -177,14 +153,6 @@ export {
177
153
  type ManifestPosition,
178
154
  renderManifestPath,
179
155
  } from "./domain/manifest-location.js";
180
- export {
181
- type Diagnostic,
182
- indexByFile,
183
- parseReport,
184
- type ParseReportOptions,
185
- type ReportFormat,
186
- uniqueDiagnostics,
187
- } from "./domain/report.js";
188
156
  export {
189
157
  CONFORMANCE_MEASURES,
190
158
  type ConformanceMeasure,
@@ -195,7 +163,10 @@ export {
195
163
  type SnapshotCampaign,
196
164
  type SnapshotConcentration,
197
165
  snapshotJsonSchema,
166
+ type SnapshotObjective,
167
+ type SnapshotPhase,
198
168
  Snapshot as SnapshotSchema,
169
+ type SnapshotSector,
199
170
  type SnapshotSlack,
200
171
  type SnapshotVacancy,
201
172
  type SnapshotViolation,
@@ -206,10 +177,6 @@ export {
206
177
  type Violation,
207
178
  type ViolationKind,
208
179
  } from "./domain/violation.js";
209
- export {
210
- loadCampaignFunctions,
211
- type LoadedCampaignFunctions,
212
- } from "./infrastructure/campaign-functions.js";
213
180
  export { makeFileSystemLive } from "./infrastructure/file-system-live.js";
214
181
  export {
215
182
  findManifestFile,
@@ -224,16 +191,25 @@ export {
224
191
  type IncludeReader,
225
192
  type SourceDocument,
226
193
  } from "./infrastructure/manifest-include.js";
227
- export { makeReportSourceLive } from "./infrastructure/report-source-live.js";
228
194
  export {
229
195
  listPackageRoots,
230
196
  listSourceFiles,
197
+ listWorkspaceProjects,
231
198
  type PackagedLanguage,
232
199
  type PackageRoot,
233
200
  type WalkedLanguage,
201
+ type WorkspaceProject,
234
202
  } from "./infrastructure/walk.js";
203
+ export {
204
+ type ExtensionContext,
205
+ type ExtensionRoute,
206
+ type LoadedExtension,
207
+ type PolicyExtension,
208
+ } from "./load/extension.js";
235
209
  export { type LoadedPolicy, loadPolicy, type LoadPolicyInput } from "./load/policy.js";
236
210
  export {
211
+ CONVENTIONS,
212
+ expandAliases,
237
213
  type LoweredRules,
238
214
  lowerManifest,
239
215
  type LowerOptions,
@@ -247,6 +223,15 @@ export {
247
223
  originOf,
248
224
  type Substitution,
249
225
  } from "./manifest/expand.js";
226
+ export { type ManifestExtension } from "./manifest/extension.js";
227
+ export {
228
+ anchored,
229
+ type CaptureIndex,
230
+ type GlobCompilation,
231
+ globToRegExp,
232
+ globToRegexSource,
233
+ prefixed,
234
+ } from "./manifest/glob.js";
250
235
  export {
251
236
  type Candidate,
252
237
  candidatesOf,
@@ -263,26 +248,20 @@ export {
263
248
  MANIFEST_NODE_SCHEMA_ID,
264
249
  MANIFEST_SCHEMA_ID,
265
250
  manifestJsonSchema,
251
+ type ManifestJsonSchemaOptions,
266
252
  manifestNodeJsonSchema,
267
253
  } from "./manifest/json-schema.js";
268
254
  export {
269
- type CampaignSpec,
270
255
  type DecodedManifest,
271
256
  decodeManifest,
272
257
  type DecodeManifestOptions,
273
- DEFAULT_LEDGER_DIR,
274
- type DetectorSpec,
275
- durationMs,
258
+ decodeManifestTree,
259
+ globsOf,
276
260
  type Manifest,
277
261
  type ManifestNode,
278
262
  Manifest as ManifestSchema,
263
+ SurfaceConvention,
279
264
  } from "./manifest/manifest.js";
280
- export {
281
- type CampaignPredicate,
282
- type CampaignPredicateInput,
283
- type CampaignSubject,
284
- type Range,
285
- } from "./ports/campaign-predicate.js";
286
265
  export { type FactExtractor } from "./ports/fact-extractor.js";
287
266
  export { type FileSystem } from "./ports/file-system.js";
288
267
  export { type Language } from "./ports/language.js";
@@ -291,12 +270,6 @@ export {
291
270
  type ModuleResolver,
292
271
  type ResolvedTarget,
293
272
  } from "./ports/module-resolver.js";
294
- export {
295
- NO_REPORTS,
296
- type ReportSource,
297
- reportSourcesOf,
298
- type ReportSpec,
299
- } from "./ports/report-source.js";
300
273
  export {
301
274
  type Position,
302
275
  type SyntaxMatch,
@@ -10,5 +10,17 @@ export const makeFileSystemFake = (
10
10
  return {
11
11
  exists: (repoRelativePath) => paths.has(repoRelativePath),
12
12
  readText: (repoRelativePath) => contents[repoRelativePath] ?? null,
13
+ list: (repoRelativeDir) => {
14
+ const prefix = repoRelativeDir === "" ? "" : `${repoRelativeDir}/`;
15
+ return [
16
+ ...new Set(
17
+ [...paths]
18
+ .filter((one) => one.startsWith(prefix))
19
+ .map((one) => one.slice(prefix.length).split("/")[0] ?? ""),
20
+ ),
21
+ ]
22
+ .filter((one) => one !== "")
23
+ .sort();
24
+ },
13
25
  };
14
26
  };
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
4
  import type { FileSystem } from "../ports/file-system.js";
@@ -23,5 +23,12 @@ export const makeFileSystemLive = (repoRoot: string): FileSystem => {
23
23
  return null;
24
24
  }
25
25
  },
26
+ list: (repoRelativeDir) => {
27
+ try {
28
+ return readdirSync(path.resolve(repoRoot, repoRelativeDir)).sort();
29
+ } catch {
30
+ return [];
31
+ }
32
+ },
26
33
  };
27
34
  };
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
4
  import type { Language } from "../ports/language.js";
@@ -24,13 +24,16 @@ export type WalkedLanguage = Pick<Language, "extensions" | "ignoredFiles">;
24
24
  // Which files count as source is the languages' to say: the extension set is
25
25
  // the union of theirs, and so is the set of files to step over. A root that
26
26
  // names a file is that file, whatever its extension — `architecture check
27
- // <file>` is a reasonable thing to type.
27
+ // <file>` is a reasonable thing to type. `widened` adds the extensions a
28
+ // campaign's scope names beyond the packs' — a file the pack cannot parse
29
+ // contributes no facts, and only a campaign that asked for it sees it.
28
30
  export const listSourceFiles = (
29
31
  repoRoot: string,
30
32
  roots: ReadonlyArray<string>,
31
33
  languages: ReadonlyArray<WalkedLanguage>,
34
+ widened: ReadonlyArray<string> = [],
32
35
  ): ReadonlyArray<string> => {
33
- const extensions = new Set(languages.flatMap((language) => language.extensions));
36
+ const extensions = new Set([...languages.flatMap((language) => language.extensions), ...widened]);
34
37
  const ignored = languages.flatMap((language) => language.ignoredFiles);
35
38
  const isSource = (entry: string): boolean =>
36
39
  extensions.has(path.extname(entry)) && !ignored.some((pattern) => pattern.test(entry));
@@ -121,3 +124,55 @@ export const listPackageRoots = (
121
124
  }
122
125
  return [...found.values()].sort((a, b) => a.root.localeCompare(b.root));
123
126
  };
127
+
128
+ export type WorkspaceProject = {
129
+ readonly name: string;
130
+ // Repo-relative folder with forward slashes.
131
+ readonly root: string;
132
+ };
133
+
134
+ // The workspace's projects, for an `nx` perimeter: every folder under the
135
+ // roots holding a `project.json`, named by it — or, when it names nothing,
136
+ // by the `package.json` beside it, or the folder. Read from the files
137
+ // rather than from `nx graph`, so no process is spawned and a checkout
138
+ // without the workspace's tooling installed still has its sectors.
139
+ export const listWorkspaceProjects = (
140
+ repoRoot: string,
141
+ roots: ReadonlyArray<string>,
142
+ ): ReadonlyArray<WorkspaceProject> => {
143
+ const found: Array<WorkspaceProject> = [];
144
+ const nameIn = (absolute: string, file: string): string | null => {
145
+ try {
146
+ const parsed = JSON.parse(readFileSync(path.join(absolute, file), "utf8")) as unknown;
147
+ const name =
148
+ typeof parsed === "object" && parsed !== null
149
+ ? (parsed as { readonly name?: unknown }).name
150
+ : undefined;
151
+ return typeof name === "string" && name !== "" ? name : null;
152
+ } catch {
153
+ return null;
154
+ }
155
+ };
156
+ const walk = (absolute: string): void => {
157
+ if (existsSync(path.join(absolute, "project.json"))) {
158
+ const root = path.relative(repoRoot, absolute).replaceAll(path.sep, "/");
159
+ found.push({
160
+ name:
161
+ nameIn(absolute, "project.json") ??
162
+ nameIn(absolute, "package.json") ??
163
+ path.basename(absolute),
164
+ root,
165
+ });
166
+ }
167
+ for (const entry of readdirSync(absolute)) {
168
+ if (SKIPPED.has(entry)) continue;
169
+ const child = path.join(absolute, entry);
170
+ if (statSync(child).isDirectory()) walk(child);
171
+ }
172
+ };
173
+ for (const root of roots) {
174
+ const absolute = path.resolve(repoRoot, root);
175
+ if (statSync(absolute).isDirectory()) walk(absolute);
176
+ }
177
+ return found.sort((a, b) => a.root.localeCompare(b.root));
178
+ };
@@ -0,0 +1,66 @@
1
+ import type * as Result from "effect/Result";
2
+
3
+ import type { ResolveScope } from "../domain/architecture-config.js";
4
+ import type { ConfigInvalid, PatternInvalid } from "../domain/architecture-error.js";
5
+ import type { ManifestExtension } from "../manifest/extension.js";
6
+ import type { Manifest } from "../manifest/manifest.js";
7
+ import type { FactExtractor } from "../ports/fact-extractor.js";
8
+ import type { FileSystem } from "../ports/file-system.js";
9
+ import type { Language } from "../ports/language.js";
10
+ import type { SyntaxMatcher } from "../ports/syntax-matcher.js";
11
+
12
+ // The load half of a family the core does not own: what `loadPolicy` hands it,
13
+ // and what it hands back.
14
+ //
15
+ // A host composes the extensions it wants — `loadPolicy({ …, extensions: [
16
+ // campaignsExtension({ functions, reports }) ] })` — and reads its family's
17
+ // state back off `LoadedPolicy.extensions` through whatever typed accessor
18
+ // that package exports. The core carries the value and never looks inside it.
19
+ //
20
+ // An extension is given the same things the core's own families are given at
21
+ // load: the routing extractor and syntax matcher, the file system port, the
22
+ // language packs, and which scope covers a given file. It is not given the
23
+ // resolver, which is built after every rule has been probed — a family that
24
+ // needs one resolves at evaluation, off `LoadedPolicy`.
25
+
26
+ export type ExtensionRoute = {
27
+ readonly language: Language;
28
+ readonly scope: ResolveScope;
29
+ };
30
+
31
+ export type ExtensionContext<Spec = unknown> = {
32
+ // What this family's `decode` returned.
33
+ readonly spec: Spec;
34
+ readonly configPath: string;
35
+ readonly repoRoot: string;
36
+ // The manifest the core decoded — `resolve`, `aliases`, `tree`, the
37
+ // families the core owns. The family's own keys are in `spec`.
38
+ readonly config: Manifest;
39
+ readonly languages: ReadonlyArray<Language>;
40
+ readonly fileSystem: FileSystem;
41
+ // Routes each file to the extractor of the language whose scope covers it:
42
+ // the same one the core parsed its own probes with.
43
+ readonly extractor: FactExtractor;
44
+ // Routes each file to the syntax matcher of its scope's language; `parse`
45
+ // answers `null` for a language carrying none.
46
+ readonly syntax: SyntaxMatcher;
47
+ readonly routeFor: (file: string) => ExtensionRoute | undefined;
48
+ readonly now: number;
49
+ };
50
+
51
+ export type LoadedExtension<Value = unknown> = {
52
+ // Carried on `LoadedPolicy.extensions`, keyed by the extension's id.
53
+ readonly value: Value;
54
+ // Rules of this family that do not report their own probe. Merged into the
55
+ // one refusal the loader raises for every family at once, so a vacuous
56
+ // campaign and a vacuous import rule are reported in the same sentence.
57
+ readonly vacuous?: ReadonlyArray<string> | undefined;
58
+ };
59
+
60
+ export type PolicyExtension<Spec = unknown, Value = unknown> = ManifestExtension<Spec> & {
61
+ // A method, like `decode`, so the loader can hold every extension at one
62
+ // erased type after their specs have been decoded.
63
+ load(
64
+ context: ExtensionContext<Spec>,
65
+ ): Result.Result<LoadedExtension<Value>, ConfigInvalid | PatternInvalid>;
66
+ };