@yansirplus/cli 0.5.17

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 (47) hide show
  1. package/PUBLIC_API.md +22 -0
  2. package/README.md +34 -0
  3. package/dist/build/agent-authoring/config.d.ts +177 -0
  4. package/dist/build/agent-authoring/config.js +607 -0
  5. package/dist/build/agent-authoring/manifest-compiler.d.ts +159 -0
  6. package/dist/build/agent-authoring/manifest-compiler.js +737 -0
  7. package/dist/build/agent-authoring/shared.d.ts +10 -0
  8. package/dist/build/agent-authoring/shared.js +57 -0
  9. package/dist/build/agent-authoring/static-target.d.ts +59 -0
  10. package/dist/build/agent-authoring/static-target.js +1857 -0
  11. package/dist/build/agent-authoring.d.ts +9 -0
  12. package/dist/build/agent-authoring.js +5 -0
  13. package/dist/build/build-cli.d.ts +2 -0
  14. package/dist/build/build-cli.js +264 -0
  15. package/dist/check/algorithmic/architecture-checks.mjs +971 -0
  16. package/dist/check/algorithmic/client-boundary-checks.mjs +337 -0
  17. package/dist/check/algorithmic/convergence-smoke-checks.mjs +608 -0
  18. package/dist/check/algorithmic/distribution-checks.mjs +919 -0
  19. package/dist/check/algorithmic/owner-checks.mjs +647 -0
  20. package/dist/check/algorithmic/package-boundary-checks.mjs +985 -0
  21. package/dist/check/algorithmic/projection-boundary-checks.mjs +302 -0
  22. package/dist/check/algorithmic/repo-surface-checks.mjs +267 -0
  23. package/dist/check/algorithmic/runtime-structural-checks.mjs +264 -0
  24. package/dist/check/algorithmic/source-alias-checks.mjs +106 -0
  25. package/dist/check/algorithmic/static-target-checks.mjs +447 -0
  26. package/dist/check/algorithmic-checks.mjs +482 -0
  27. package/dist/check/check-coverage.mjs +231 -0
  28. package/dist/check/command-runner.mjs +22 -0
  29. package/dist/check/default-gate.mjs +51 -0
  30. package/dist/check/gate-selector.mjs +305 -0
  31. package/dist/check/manifest-rules.mjs +223 -0
  32. package/dist/check/package-graph.mjs +464 -0
  33. package/dist/generate/generate-agent-docs.mjs +435 -0
  34. package/dist/generate/generate-carrier-reference.mjs +514 -0
  35. package/dist/generate/generate-docs.mjs +345 -0
  36. package/dist/generate/generate-effect-skill-manifests.mjs +193 -0
  37. package/dist/generate/project-docs-site.mjs +190 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +25 -0
  40. package/dist/lib/agent-docs-model.mjs +888 -0
  41. package/dist/lib/boundary-rules.mjs +63 -0
  42. package/dist/lib/capability-routes.mjs +354 -0
  43. package/dist/lib/projection-sink.mjs +113 -0
  44. package/dist/lib/public-api-model.mjs +306 -0
  45. package/dist/main.mjs +233 -0
  46. package/dist/runner.mjs +127 -0
  47. package/package.json +32 -0
@@ -0,0 +1,919 @@
1
+ export const createDistributionChecks = ({
2
+ fs,
3
+ path,
4
+ ts,
5
+ repoRoot,
6
+ compare,
7
+ isRecord,
8
+ readJson,
9
+ walk,
10
+ failIfAny,
11
+ importSpecifierRecords,
12
+ graphWorkspacePackageRecords,
13
+ sourceModuleGraph,
14
+ moduleBucketRegistry,
15
+ packageUnitsRegistryFindings,
16
+ distributionRootsRegistryFindings,
17
+ packageUnitsRegistryPath,
18
+ distributionRootsRegistryPath,
19
+ distributionMinimalityFailures,
20
+ }) => {
21
+ const distributionInstallScriptNames = new Set([
22
+ "install",
23
+ "postinstall",
24
+ "preinstall",
25
+ "prepare",
26
+ ]);
27
+ const distributionPackageWideMetadataFields = ["engines", "os", "cpu", "libc"];
28
+ const distributionNativeToolPattern =
29
+ /\b(?:node-gyp|prebuild(?:ify|-install)?|cmake-js|node-pre-gyp)\b/u;
30
+ const distributionNativeFilePattern = /(?:^|\/)(?:binding\.gyp|CMakeLists\.txt)$|\.node$/u;
31
+ const distributionSourcePattern = /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs|d\.ts)$/u;
32
+
33
+ const distributionFinding = ({
34
+ kind,
35
+ severity = "splitter",
36
+ file,
37
+ packageName,
38
+ message,
39
+ specifier,
40
+ target,
41
+ line,
42
+ column,
43
+ }) => ({
44
+ kind,
45
+ severity,
46
+ file,
47
+ packageName,
48
+ message,
49
+ specifier,
50
+ target,
51
+ line,
52
+ column,
53
+ });
54
+
55
+ const manifestSectionEntries = (manifest, section) =>
56
+ Object.entries(isRecord(manifest[section]) ? manifest[section] : {}).sort(([left], [right]) =>
57
+ compare(left, right),
58
+ );
59
+
60
+ const isInternalPackageDependency = (name) => name.startsWith("@agent-os/");
61
+
62
+ const optionalPeerNames = (manifest) =>
63
+ new Set(
64
+ manifestSectionEntries(manifest, "peerDependencies")
65
+ .filter(([name]) => manifest.peerDependenciesMeta?.[name]?.optional === true)
66
+ .map(([name]) => name),
67
+ );
68
+
69
+ const peerSpecifierMatches = (specifier, peerName) =>
70
+ specifier === peerName || specifier.startsWith(`${peerName}/`);
71
+
72
+ const distributionManifestFindings = (record, manifest, packageFiles = []) => {
73
+ const findings = [];
74
+ const packageJson = `${record.path}/package.json`;
75
+
76
+ for (const [scriptName, scriptValue] of manifestSectionEntries(manifest, "scripts")) {
77
+ if (!distributionInstallScriptNames.has(scriptName)) continue;
78
+ findings.push(
79
+ distributionFinding({
80
+ kind: "package-install-script",
81
+ file: packageJson,
82
+ packageName: record.name,
83
+ message: `package-wide ${scriptName} script executes during install lifecycle`,
84
+ target: scriptValue,
85
+ }),
86
+ );
87
+ }
88
+
89
+ if (manifest.gypfile !== undefined) {
90
+ findings.push(
91
+ distributionFinding({
92
+ kind: "native-marker",
93
+ file: packageJson,
94
+ packageName: record.name,
95
+ message: "package manifest declares gypfile native build metadata",
96
+ target: String(manifest.gypfile),
97
+ }),
98
+ );
99
+ }
100
+ for (const file of packageFiles.filter((entry) => distributionNativeFilePattern.test(entry))) {
101
+ findings.push(
102
+ distributionFinding({
103
+ kind: "native-marker",
104
+ file,
105
+ packageName: record.name,
106
+ message: "package contains native build or native artifact marker",
107
+ target: path.basename(file),
108
+ }),
109
+ );
110
+ }
111
+ for (const section of ["dependencies", "optionalDependencies", "devDependencies"]) {
112
+ for (const [name, version] of manifestSectionEntries(manifest, section)) {
113
+ if (!distributionNativeToolPattern.test(`${name} ${String(version)}`)) continue;
114
+ findings.push(
115
+ distributionFinding({
116
+ kind: "native-tool-dependency",
117
+ severity: section === "devDependencies" ? "info" : "splitter",
118
+ file: packageJson,
119
+ packageName: record.name,
120
+ message: `${section}.${name} carries native build tooling`,
121
+ specifier: name,
122
+ target: version,
123
+ }),
124
+ );
125
+ }
126
+ }
127
+
128
+ for (const field of distributionPackageWideMetadataFields) {
129
+ if (manifest[field] === undefined) continue;
130
+ findings.push(
131
+ distributionFinding({
132
+ kind: "package-wide-metadata",
133
+ file: packageJson,
134
+ packageName: record.name,
135
+ message: `${field} is a package-wide install constraint and cannot be subpath-localized`,
136
+ target: JSON.stringify(manifest[field]),
137
+ }),
138
+ );
139
+ }
140
+
141
+ for (const section of ["dependencies", "optionalDependencies"]) {
142
+ for (const [name, version] of manifestSectionEntries(manifest, section)) {
143
+ if (isInternalPackageDependency(name)) continue;
144
+ findings.push(
145
+ distributionFinding({
146
+ kind: "hard-dependency",
147
+ file: packageJson,
148
+ packageName: record.name,
149
+ message: `${section}.${name} is installed for every consumer of the package`,
150
+ specifier: name,
151
+ target: version,
152
+ }),
153
+ );
154
+ }
155
+ }
156
+
157
+ for (const [name, version] of manifestSectionEntries(manifest, "peerDependencies")) {
158
+ const optional = manifest.peerDependenciesMeta?.[name]?.optional === true;
159
+ findings.push(
160
+ distributionFinding({
161
+ kind: optional ? "optional-peer" : "required-peer",
162
+ severity: optional ? "info" : "splitter",
163
+ file: packageJson,
164
+ packageName: record.name,
165
+ message: optional
166
+ ? `${name} is localizable when every import remains behind explicit subpath exports`
167
+ : `${name} is a package-wide peer obligation`,
168
+ specifier: name,
169
+ target: version,
170
+ }),
171
+ );
172
+ }
173
+
174
+ return findings;
175
+ };
176
+
177
+ const distributionScriptKindForFile = (file) => {
178
+ if (file.endsWith(".tsx")) return ts.ScriptKind.TSX;
179
+ if (file.endsWith(".jsx")) return ts.ScriptKind.JSX;
180
+ if (file.endsWith(".js") || file.endsWith(".mjs") || file.endsWith(".cjs")) {
181
+ return ts.ScriptKind.JS;
182
+ }
183
+ return ts.ScriptKind.TS;
184
+ };
185
+
186
+ const distributionNodePosition = (sourceFile, node) => {
187
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
188
+ return { line: position.line + 1, column: position.character + 1 };
189
+ };
190
+
191
+ const distributionExpressionText = (sourceFile, node) =>
192
+ node.getText(sourceFile).replace(/\s+/gu, " ").slice(0, 160);
193
+
194
+ const distributionExpressionName = (expression) => {
195
+ if (ts.isIdentifier(expression)) return expression.text;
196
+ if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
197
+ return undefined;
198
+ };
199
+
200
+ const distributionRootIdentifier = (node) => {
201
+ let current = node;
202
+ while (ts.isPropertyAccessExpression(current) || ts.isElementAccessExpression(current)) {
203
+ current = current.expression;
204
+ }
205
+ return ts.isIdentifier(current) ? current.text : undefined;
206
+ };
207
+
208
+ const distributionTouchesAmbientGlobal = (node) => {
209
+ if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
210
+ const root = distributionRootIdentifier(node);
211
+ const text = node.getText();
212
+ if (root === "globalThis" || root === "window" || root === "document") return true;
213
+ if (root === "process" && text.startsWith("process.env")) return true;
214
+ }
215
+ let found = false;
216
+ ts.forEachChild(node, (child) => {
217
+ if (!found && distributionTouchesAmbientGlobal(child)) found = true;
218
+ });
219
+ return found;
220
+ };
221
+
222
+ const distributionContainsLoadSideEffect = (node) => {
223
+ if (
224
+ ts.isArrowFunction(node) ||
225
+ ts.isFunctionDeclaration(node) ||
226
+ ts.isFunctionExpression(node) ||
227
+ ts.isMethodDeclaration(node) ||
228
+ ts.isClassDeclaration(node) ||
229
+ ts.isClassExpression(node)
230
+ ) {
231
+ return false;
232
+ }
233
+ if (ts.isBinaryExpression(node) && distributionTouchesAmbientGlobal(node.left)) return true;
234
+ if (
235
+ (ts.isPostfixUnaryExpression(node) || ts.isPrefixUnaryExpression(node)) &&
236
+ distributionTouchesAmbientGlobal(node.operand)
237
+ ) {
238
+ return true;
239
+ }
240
+ if (
241
+ ts.isCallExpression(node) &&
242
+ [
243
+ "exec",
244
+ "execFile",
245
+ "fork",
246
+ "mkdirSync",
247
+ "rmSync",
248
+ "spawn",
249
+ "spawnSync",
250
+ "writeFileSync",
251
+ ].includes(distributionExpressionName(node.expression) ?? "")
252
+ ) {
253
+ return true;
254
+ }
255
+ if (
256
+ ts.isNewExpression(node) &&
257
+ ts.isIdentifier(node.expression) &&
258
+ node.expression.text === "Worker"
259
+ ) {
260
+ return true;
261
+ }
262
+ let found = false;
263
+ ts.forEachChild(node, (child) => {
264
+ if (!found && distributionContainsLoadSideEffect(child)) found = true;
265
+ });
266
+ return found;
267
+ };
268
+
269
+ const distributionSourceProbeFindingsForSource = (content, file, packageName) => {
270
+ const sourceFile = ts.createSourceFile(
271
+ file,
272
+ content,
273
+ ts.ScriptTarget.Latest,
274
+ true,
275
+ distributionScriptKindForFile(file),
276
+ );
277
+ const findings = [];
278
+ const recordNode = (kind, node, message) => {
279
+ const position = distributionNodePosition(sourceFile, node);
280
+ findings.push(
281
+ distributionFinding({
282
+ kind,
283
+ file,
284
+ packageName,
285
+ message,
286
+ target: distributionExpressionText(sourceFile, node),
287
+ line: position.line,
288
+ column: position.column,
289
+ }),
290
+ );
291
+ };
292
+ const visitAugmentation = (node) => {
293
+ if (ts.isModuleDeclaration(node)) {
294
+ if (node.name.kind === ts.SyntaxKind.GlobalKeyword) {
295
+ recordNode("global-type-augmentation", node, "source declares global type augmentation");
296
+ } else if (ts.isStringLiteralLike(node.name)) {
297
+ recordNode("module-type-augmentation", node, "source declares module type augmentation");
298
+ } else if (ts.isIdentifier(node.name) && node.name.text === "NodeJS") {
299
+ recordNode("global-type-augmentation", node, "source augments NodeJS namespace types");
300
+ }
301
+ }
302
+ if (
303
+ ts.isInterfaceDeclaration(node) &&
304
+ ["Document", "ImportMeta", "ProcessEnv", "Window"].includes(node.name.text)
305
+ ) {
306
+ recordNode("global-type-augmentation", node, `source declares ambient ${node.name.text}`);
307
+ }
308
+ ts.forEachChild(node, visitAugmentation);
309
+ };
310
+ visitAugmentation(sourceFile);
311
+
312
+ for (const statement of sourceFile.statements) {
313
+ if (
314
+ (ts.isExpressionStatement(statement) &&
315
+ distributionContainsLoadSideEffect(statement.expression)) ||
316
+ (ts.isVariableStatement(statement) && distributionContainsLoadSideEffect(statement))
317
+ ) {
318
+ recordNode(
319
+ "package-load-side-effect",
320
+ statement,
321
+ "top-level source touches ambient process/global state or host execution",
322
+ );
323
+ }
324
+ }
325
+ return findings;
326
+ };
327
+
328
+ const distributionExportTargets = (target) => {
329
+ if (typeof target === "string") return [target];
330
+ if (!isRecord(target)) return [];
331
+ return Object.values(target).flatMap((value) => distributionExportTargets(value));
332
+ };
333
+
334
+ const distributionExportEntries = (record, manifest) => {
335
+ const exportsValue = manifest.exports;
336
+ if (exportsValue === undefined) {
337
+ return fs.existsSync(path.join(repoRoot, record.path, "src/index.ts"))
338
+ ? [{ subpath: ".", targets: [`${record.path}/src/index.ts`] }]
339
+ : [];
340
+ }
341
+ const entries = isRecord(exportsValue) ? Object.entries(exportsValue) : [[".", exportsValue]];
342
+ return entries
343
+ .map(([subpath, target]) => ({
344
+ subpath,
345
+ targets: distributionExportTargets(target)
346
+ .filter((entry) => entry.startsWith("./"))
347
+ .map((entry) => path.join(record.path, entry).split(path.sep).join("/"))
348
+ .filter((entry) => distributionSourcePattern.test(entry)),
349
+ }))
350
+ .filter((entry) => entry.targets.length > 0)
351
+ .sort((left, right) => compare(left.subpath, right.subpath));
352
+ };
353
+
354
+ const distributionClosureForRoots = (roots, edges) => {
355
+ const byFrom = new Map();
356
+ for (const edge of edges) {
357
+ byFrom.set(edge.fromFile, [...(byFrom.get(edge.fromFile) ?? []), edge.toFile]);
358
+ }
359
+ const visited = new Set();
360
+ const pending = [...roots].sort(compare);
361
+ while (pending.length > 0) {
362
+ const file = pending.shift();
363
+ if (file === undefined || visited.has(file)) continue;
364
+ visited.add(file);
365
+ for (const target of byFrom.get(file) ?? []) {
366
+ if (!visited.has(target)) pending.push(target);
367
+ }
368
+ pending.sort(compare);
369
+ }
370
+ return visited;
371
+ };
372
+
373
+ const distributionPeerImportsInFiles = (sourceByFile, files, peerNames) => {
374
+ const imports = [];
375
+ for (const file of [...files].sort(compare)) {
376
+ const source = sourceByFile.get(file);
377
+ if (source === undefined) continue;
378
+ for (const importRecord of importSpecifierRecords(source, file)) {
379
+ for (const peerName of peerNames) {
380
+ if (!peerSpecifierMatches(importRecord.specifier, peerName)) continue;
381
+ imports.push({ file, peerName, ...importRecord });
382
+ }
383
+ }
384
+ }
385
+ return imports;
386
+ };
387
+
388
+ const distributionSubpathFindings = ({ record, manifest, sourceByFile, edges }) => {
389
+ const optionalPeers = optionalPeerNames(manifest);
390
+ if (optionalPeers.size === 0) return [];
391
+ const exportEntries = distributionExportEntries(record, manifest);
392
+ const samePackageEdges = edges.filter(
393
+ (edge) => edge.from.name === record.name && edge.to.name === record.name,
394
+ );
395
+ const findings = [];
396
+ const rootEntry = exportEntries.find((entry) => entry.subpath === ".");
397
+ const rootClosure =
398
+ rootEntry === undefined
399
+ ? new Set()
400
+ : distributionClosureForRoots(rootEntry.targets, samePackageEdges);
401
+ for (const importRecord of distributionPeerImportsInFiles(
402
+ sourceByFile,
403
+ rootClosure,
404
+ optionalPeers,
405
+ )) {
406
+ const typeOnly =
407
+ importRecord.importKind === "type" ||
408
+ importRecord.syntaxKind === "import-type" ||
409
+ importRecord.syntaxKind === "export";
410
+ findings.push(
411
+ distributionFinding({
412
+ kind: typeOnly ? "root-dts-peer-type-leak" : "root-subpath-peer-leak",
413
+ file: importRecord.file,
414
+ packageName: record.name,
415
+ message: `package root closure reaches optional peer ${importRecord.peerName}; move it behind an explicit subpath`,
416
+ specifier: importRecord.specifier,
417
+ target: rootEntry?.subpath ?? ".",
418
+ line: importRecord.line,
419
+ column: importRecord.column,
420
+ }),
421
+ );
422
+ }
423
+
424
+ for (const peerName of [...optionalPeers].sort(compare)) {
425
+ const subpaths = [];
426
+ for (const entry of exportEntries.filter((candidate) => candidate.subpath !== ".")) {
427
+ const closure = distributionClosureForRoots(entry.targets, samePackageEdges);
428
+ const imports = distributionPeerImportsInFiles(sourceByFile, closure, new Set([peerName]));
429
+ if (imports.length > 0) subpaths.push(entry.subpath);
430
+ }
431
+ if (subpaths.length === 0) continue;
432
+ findings.push(
433
+ distributionFinding({
434
+ kind: "optional-peer-locality",
435
+ severity: "info",
436
+ file: `${record.path}/package.json`,
437
+ packageName: record.name,
438
+ message: `${peerName} is only needed by explicit subpath closure(s)`,
439
+ specifier: peerName,
440
+ target: subpaths.join(", "),
441
+ }),
442
+ );
443
+ }
444
+
445
+ return findings;
446
+ };
447
+
448
+ const distributionFindingsForPackage = ({
449
+ record,
450
+ manifest,
451
+ packageFiles = [],
452
+ sourceByFile = new Map(),
453
+ edges = [],
454
+ }) => [
455
+ ...distributionManifestFindings(record, manifest, packageFiles),
456
+ ...[...sourceByFile.entries()].flatMap(([file, source]) =>
457
+ distributionSourceProbeFindingsForSource(source, file, record.name),
458
+ ),
459
+ ...distributionSubpathFindings({ record, manifest, sourceByFile, edges }),
460
+ ];
461
+
462
+ const distributionEffectPeerFindings = (records, expectedRange) => {
463
+ const ranges = new Map();
464
+ const findings = [];
465
+ for (const { record, manifest } of records) {
466
+ const range = manifest.peerDependencies?.effect;
467
+ if (typeof range !== "string") continue;
468
+ ranges.set(range, [...(ranges.get(range) ?? []), record]);
469
+ }
470
+ const expected = expectedRange ?? [...ranges.keys()].sort(compare)[0];
471
+ if (expected === undefined || (expectedRange === undefined && ranges.size <= 1))
472
+ return findings;
473
+ for (const [range, rangeRecords] of [...ranges.entries()].sort(([left], [right]) =>
474
+ compare(left, right),
475
+ )) {
476
+ if (range === expected) continue;
477
+ for (const record of rangeRecords) {
478
+ findings.push(
479
+ distributionFinding({
480
+ kind: "effect-peer-invariant",
481
+ file: `${record.path}/package.json`,
482
+ packageName: record.name,
483
+ message: `effect peer range ${range} differs from single-source range ${expected}`,
484
+ specifier: "effect",
485
+ target: range,
486
+ }),
487
+ );
488
+ }
489
+ }
490
+ return findings;
491
+ };
492
+
493
+ const hardInstallEntryName = (entry) => {
494
+ if (typeof entry === "string") return entry;
495
+ if (isRecord(entry) && typeof entry.name === "string") return entry.name;
496
+ return undefined;
497
+ };
498
+
499
+ const packageUnitHardDependencyNames = (unit) => {
500
+ const envelope = unit.hardInstallEnvelope;
501
+ if (!isRecord(envelope)) return new Set();
502
+ return new Set(
503
+ (Array.isArray(envelope.dependencies)
504
+ ? envelope.dependencies.map(hardInstallEntryName)
505
+ : []
506
+ ).filter((value) => typeof value === "string" && value.length > 0),
507
+ );
508
+ };
509
+
510
+ const packageUnitRequiredPeers = (unit) =>
511
+ Array.isArray(unit.hardInstallEnvelope?.requiredPeers)
512
+ ? unit.hardInstallEnvelope.requiredPeers.filter(
513
+ (peer) =>
514
+ isRecord(peer) && typeof peer.name === "string" && typeof peer.range === "string",
515
+ )
516
+ : [];
517
+
518
+ const packageUnitOptionalPeerEntries = (unit) =>
519
+ Array.isArray(unit.publicSubpaths)
520
+ ? unit.publicSubpaths.flatMap((subpath) =>
521
+ Array.isArray(subpath.optionalPeers)
522
+ ? subpath.optionalPeers
523
+ .filter((peer) => typeof peer === "string" && peer.length > 0)
524
+ .map((peer) => ({ peer, subpath: subpath.subpath }))
525
+ : [],
526
+ )
527
+ : [];
528
+
529
+ const distributionUnitFinding = ({ kind, unit, message, specifier, target }) =>
530
+ distributionFinding({
531
+ kind,
532
+ file: packageUnitsRegistryPath,
533
+ packageName: isRecord(unit) && typeof unit.id === "string" ? unit.id : undefined,
534
+ message,
535
+ specifier,
536
+ target,
537
+ });
538
+
539
+ const distributionUnitRegistryFindings = ({ registry, expectedEffectRange }) => {
540
+ if (!isRecord(registry) || !Array.isArray(registry.packageUnits)) return [];
541
+ const findings = [];
542
+ for (const unit of registry.packageUnits.filter(isRecord)) {
543
+ const rootSubpaths = Array.isArray(unit.publicSubpaths)
544
+ ? unit.publicSubpaths.filter((subpath) => subpath.subpath === ".")
545
+ : [];
546
+ if (rootSubpaths.length !== 1) {
547
+ findings.push(
548
+ distributionUnitFinding({
549
+ kind: "package-unit-root-export",
550
+ unit,
551
+ message: "package unit must declare exactly one root public subpath",
552
+ target: String(rootSubpaths.length),
553
+ }),
554
+ );
555
+ }
556
+ for (const rootSubpath of rootSubpaths) {
557
+ const rootOptionalPeers = Array.isArray(rootSubpath.optionalPeers)
558
+ ? rootSubpath.optionalPeers.filter((peer) => typeof peer === "string" && peer.length > 0)
559
+ : [];
560
+ for (const peer of rootOptionalPeers) {
561
+ findings.push(
562
+ distributionUnitFinding({
563
+ kind: "package-unit-root-optional-peer",
564
+ unit,
565
+ message: "package root cannot require a subpath-local optional peer",
566
+ specifier: peer,
567
+ target: ".",
568
+ }),
569
+ );
570
+ }
571
+ }
572
+
573
+ const hardDependencyNames = packageUnitHardDependencyNames(unit);
574
+ const requiredPeers = packageUnitRequiredPeers(unit);
575
+ const requiredPeerNames = new Set(requiredPeers.map((peer) => peer.name));
576
+ const optionalPeerEntries = packageUnitOptionalPeerEntries(unit);
577
+ const optionalPeerKeys = new Set();
578
+ for (const { peer, subpath } of optionalPeerEntries) {
579
+ const key = `${subpath}\0${peer}`;
580
+ if (optionalPeerKeys.has(key)) {
581
+ findings.push(
582
+ distributionUnitFinding({
583
+ kind: "package-unit-optional-peer-duplicate",
584
+ unit,
585
+ message: "subpath optionalPeers must not contain duplicate peers",
586
+ specifier: peer,
587
+ target: subpath,
588
+ }),
589
+ );
590
+ }
591
+ optionalPeerKeys.add(key);
592
+ if (hardDependencyNames.has(peer) || requiredPeerNames.has(peer)) {
593
+ findings.push(
594
+ distributionUnitFinding({
595
+ kind: "package-unit-hard-locality",
596
+ unit,
597
+ message: "subpath-local optional peer is also declared as a package-wide obligation",
598
+ specifier: peer,
599
+ target: subpath,
600
+ }),
601
+ );
602
+ }
603
+ if (peer === "effect") {
604
+ findings.push(
605
+ distributionUnitFinding({
606
+ kind: "package-unit-effect-peer-invariant",
607
+ unit,
608
+ message:
609
+ "effect is a single package-wide peer invariant, not a subpath-local optional peer",
610
+ specifier: peer,
611
+ target: subpath,
612
+ }),
613
+ );
614
+ }
615
+ }
616
+
617
+ for (const peer of requiredPeers.filter((entry) => entry.name === "effect")) {
618
+ if (expectedEffectRange !== undefined && peer.range !== expectedEffectRange) {
619
+ findings.push(
620
+ distributionUnitFinding({
621
+ kind: "package-unit-effect-peer-invariant",
622
+ unit,
623
+ message: `effect peer range must match root catalog single source ${expectedEffectRange}`,
624
+ specifier: "effect",
625
+ target: peer.range,
626
+ }),
627
+ );
628
+ }
629
+ }
630
+ }
631
+ return findings;
632
+ };
633
+
634
+ const distributionArchitectureFailures = () => {
635
+ const workspacePackageRecords = graphWorkspacePackageRecords(repoRoot).filter(
636
+ (record) => typeof record.name === "string" && record.name.startsWith("@agent-os/"),
637
+ );
638
+ const workspacePackageRecordsByName = new Map(
639
+ workspacePackageRecords.map((record) => [record.name, record]),
640
+ );
641
+ const moduleBuckets = moduleBucketRegistry();
642
+ const packageUnits = readJson(packageUnitsRegistryPath);
643
+ const distributionRoots = readJson(distributionRootsRegistryPath);
644
+ const packageUnitsById = new Map(
645
+ Array.isArray(packageUnits.packageUnits)
646
+ ? packageUnits.packageUnits
647
+ .filter(isRecord)
648
+ .map((unit) => [unit.id, unit])
649
+ .filter(([id]) => typeof id === "string")
650
+ : [],
651
+ );
652
+ const bucketIds = new Set(
653
+ Array.isArray(moduleBuckets.buckets) ? moduleBuckets.buckets.map((bucket) => bucket.id) : [],
654
+ );
655
+ const ambientIds = new Set(
656
+ Array.isArray(moduleBuckets.ambients)
657
+ ? moduleBuckets.ambients.map((ambient) => ambient.id)
658
+ : [],
659
+ );
660
+ const packageUnitIds = new Set(
661
+ Array.isArray(packageUnits.packageUnits)
662
+ ? packageUnits.packageUnits.map((unit) => unit.id)
663
+ : [],
664
+ );
665
+ const targetProfileIds = new Set(
666
+ Array.isArray(distributionRoots.targetProfiles)
667
+ ? distributionRoots.targetProfiles.map((profile) => profile.id)
668
+ : [],
669
+ );
670
+ const expectedEffectRange = readJson("package.json").catalog?.effect;
671
+ return [
672
+ ...packageUnitsRegistryFindings({
673
+ registry: packageUnits,
674
+ bucketIds,
675
+ ambientIds,
676
+ targetProfileIds,
677
+ workspacePackageRecordsByName,
678
+ }),
679
+ ...distributionRootsRegistryFindings({
680
+ registry: distributionRoots,
681
+ packageUnitIds,
682
+ ambientIds,
683
+ packageUnitsById,
684
+ }),
685
+ ...distributionUnitRegistryFindings({
686
+ registry: packageUnits,
687
+ expectedEffectRange,
688
+ }).map(formatDistributionFinding),
689
+ ];
690
+ };
691
+
692
+ const distributionUnitNegativeFixtureFailures = () => {
693
+ const failures = [];
694
+ const fixtureRecord = {
695
+ name: "@agent-os/runtime",
696
+ path: "packages/runtime",
697
+ };
698
+ const bucketIds = new Set(["axioms", "ledger", "projection", "adapter"]);
699
+ const ambientIds = new Set(["neutral", "browser", "node", "cloudflare-worker"]);
700
+ const targetProfileIds = new Set(["neutral", "browser", "node", "cloudflare-worker"]);
701
+ const schemaFindings = packageUnitsRegistryFindings({
702
+ registry: { schemaVersion: 2, policy: {}, packageUnits: [] },
703
+ bucketIds,
704
+ ambientIds,
705
+ targetProfileIds,
706
+ });
707
+ if (!schemaFindings.some((finding) => finding.includes("schemaVersion must be 1"))) {
708
+ failures.push(
709
+ `schema negative fixture: expected schemaVersion failure, got ${schemaFindings.join("\n")}`,
710
+ );
711
+ }
712
+
713
+ const semanticFindings = distributionUnitRegistryFindings({
714
+ expectedEffectRange: "^4.0.0",
715
+ registry: {
716
+ packageUnits: [
717
+ {
718
+ id: "client",
719
+ hardInstallEnvelope: {
720
+ dependencies: ["react"],
721
+ installScripts: [],
722
+ nativeArtifacts: [],
723
+ packageWideMetadata: [],
724
+ requiredPeers: [{ name: "effect", range: "^5.0.0" }],
725
+ },
726
+ publicSubpaths: [
727
+ { subpath: ".", optionalPeers: ["react"] },
728
+ { subpath: "./react", optionalPeers: ["react", "react"] },
729
+ { subpath: "./effect", optionalPeers: ["effect"] },
730
+ ],
731
+ },
732
+ ],
733
+ },
734
+ });
735
+ for (const kind of [
736
+ "package-unit-root-optional-peer",
737
+ "package-unit-hard-locality",
738
+ "package-unit-optional-peer-duplicate",
739
+ "package-unit-effect-peer-invariant",
740
+ ]) {
741
+ if (!semanticFindings.some((finding) => finding.kind === kind)) {
742
+ failures.push(
743
+ `semantic negative fixture: expected ${kind}, got ${JSON.stringify(
744
+ semanticFindings.map((finding) => finding.kind),
745
+ )}`,
746
+ );
747
+ }
748
+ }
749
+
750
+ const leakFindings = distributionFindingsForPackage({
751
+ record: fixtureRecord,
752
+ manifest: {
753
+ peerDependencies: { react: "^19" },
754
+ peerDependenciesMeta: { react: { optional: true } },
755
+ exports: {
756
+ ".": "./src/index.ts",
757
+ "./react": "./src/react.ts",
758
+ },
759
+ },
760
+ sourceByFile: new Map([
761
+ ["packages/runtime/src/index.ts", 'export type { ReactNode } from "./react";'],
762
+ [
763
+ "packages/runtime/src/react.ts",
764
+ 'import type { ReactNode } from "react"; import { useMemo } from "react"; export type { ReactNode }; export { useMemo };',
765
+ ],
766
+ ]),
767
+ edges: [
768
+ {
769
+ from: fixtureRecord,
770
+ to: fixtureRecord,
771
+ fromFile: "packages/runtime/src/index.ts",
772
+ toFile: "packages/runtime/src/react.ts",
773
+ specifier: "./react",
774
+ },
775
+ ],
776
+ });
777
+ for (const kind of ["root-dts-peer-type-leak", "root-subpath-peer-leak"]) {
778
+ if (!leakFindings.some((finding) => finding.kind === kind)) {
779
+ failures.push(
780
+ `root leak negative fixture: expected ${kind}, got ${JSON.stringify(
781
+ leakFindings.map((finding) => finding.kind),
782
+ )}`,
783
+ );
784
+ }
785
+ }
786
+
787
+ const effectFindings = distributionEffectPeerFindings(
788
+ [
789
+ {
790
+ record: fixtureRecord,
791
+ manifest: {
792
+ peerDependencies: {
793
+ effect: "^5.0.0",
794
+ },
795
+ },
796
+ },
797
+ ],
798
+ "^4.0.0",
799
+ );
800
+ if (!effectFindings.some((finding) => finding.kind === "effect-peer-invariant")) {
801
+ failures.push(
802
+ `effect peer negative fixture: expected effect-peer-invariant, got ${JSON.stringify(
803
+ effectFindings.map((finding) => finding.kind),
804
+ )}`,
805
+ );
806
+ }
807
+
808
+ return failures;
809
+ };
810
+
811
+ const formatDistributionFinding = (finding) => {
812
+ const location =
813
+ finding.line === undefined
814
+ ? finding.file
815
+ : `${finding.file}:${finding.line}:${finding.column ?? 1}`;
816
+ const target = finding.target === undefined ? "" : ` -> ${finding.target}`;
817
+ const specifier = finding.specifier === undefined ? "" : ` via ${finding.specifier}`;
818
+ return `${location}: distribution-units:${finding.severity}:${finding.kind}: ${finding.message}${specifier}${target}`;
819
+ };
820
+
821
+ const checkDistributionUnits = (args = []) => {
822
+ const reportOnly = args.length === 1 && args[0] === "--report-only";
823
+ const negativeFixtures = args.length === 1 && args[0] === "--negative-fixtures";
824
+ const enforceMinimality = args.length === 1 && args[0] === "--enforce-minimality";
825
+ if (!reportOnly && !negativeFixtures && !enforceMinimality && args.length > 0) {
826
+ throw new Error(`distribution-units: unexpected argument(s): ${args.join(" ")}`);
827
+ }
828
+ if (negativeFixtures) {
829
+ failIfAny("distribution units negative fixtures", distributionUnitNegativeFixtureFailures());
830
+ return;
831
+ }
832
+ const records = graphWorkspacePackageRecords(repoRoot).filter(
833
+ (record) => typeof record.name === "string" && record.name.startsWith("@agent-os/"),
834
+ );
835
+ const graph = sourceModuleGraph(repoRoot, records);
836
+ const sourceByFile = new Map(
837
+ graph.files.map((entry) => [
838
+ entry.file,
839
+ fs.readFileSync(path.join(repoRoot, entry.file), "utf8"),
840
+ ]),
841
+ );
842
+ const recordsWithManifests = records.map((record) => ({
843
+ record,
844
+ manifest: readJson(`${record.path}/package.json`),
845
+ }));
846
+ const reportFindings = [
847
+ ...recordsWithManifests.flatMap(({ record, manifest }) => {
848
+ const packageSourceByFile = new Map(
849
+ [...sourceByFile.entries()].filter(([file]) => file.startsWith(`${record.path}/`)),
850
+ );
851
+ return distributionFindingsForPackage({
852
+ record,
853
+ manifest,
854
+ packageFiles: walk(record.path),
855
+ sourceByFile: packageSourceByFile,
856
+ edges: graph.edges,
857
+ });
858
+ }),
859
+ ...distributionEffectPeerFindings(
860
+ recordsWithManifests,
861
+ readJson("package.json").catalog?.effect,
862
+ ),
863
+ ].sort(
864
+ (left, right) =>
865
+ compare(left.severity, right.severity) ||
866
+ compare(left.kind, right.kind) ||
867
+ compare(left.file, right.file) ||
868
+ compare(left.specifier ?? "", right.specifier ?? ""),
869
+ );
870
+ if (reportOnly) {
871
+ const splitterCount = reportFindings.filter((finding) => finding.severity !== "info").length;
872
+ const infoCount = reportFindings.length - splitterCount;
873
+ const lines = reportFindings.map(formatDistributionFinding);
874
+ console.log(
875
+ `distribution units report-only: ${reportFindings.length} finding(s); ${splitterCount} package-wide obligation(s); ${infoCount} localizable observation(s)`,
876
+ );
877
+ for (const line of lines) console.log(line);
878
+ return;
879
+ }
880
+ const rootLeakFindings = recordsWithManifests.flatMap(({ record, manifest }) => {
881
+ const packageSourceByFile = new Map(
882
+ [...sourceByFile.entries()].filter(([file]) => file.startsWith(`${record.path}/`)),
883
+ );
884
+ return distributionSubpathFindings({
885
+ record,
886
+ manifest,
887
+ sourceByFile: packageSourceByFile,
888
+ edges: graph.edges,
889
+ }).filter((finding) => finding.kind.startsWith("root-"));
890
+ });
891
+ const effectPeerFindings = distributionEffectPeerFindings(
892
+ recordsWithManifests,
893
+ readJson("package.json").catalog?.effect,
894
+ );
895
+ const failures = [
896
+ ...distributionArchitectureFailures(),
897
+ ...rootLeakFindings.map(formatDistributionFinding),
898
+ ...effectPeerFindings.map(formatDistributionFinding),
899
+ ];
900
+ if (enforceMinimality) failures.push(...distributionMinimalityFailures());
901
+ failIfAny(enforceMinimality ? "distribution units minimality" : "distribution units", failures);
902
+ };
903
+
904
+ return {
905
+ distributionManifestFindings,
906
+ distributionSourceProbeFindingsForSource,
907
+ distributionSubpathFindings,
908
+ distributionFindingsForPackage,
909
+ distributionEffectPeerFindings,
910
+ distributionExportEntries,
911
+ distributionClosureForRoots,
912
+ distributionUnitRegistryFindings,
913
+ distributionUnitNegativeFixtureFailures,
914
+ distributionUnitFinding,
915
+ packageUnitOptionalPeerEntries,
916
+ formatDistributionFinding,
917
+ checkDistributionUnits,
918
+ };
919
+ };