@deepseek-ai/dsh-typert-generator 0.2.1-alpha.2 → 0.2.1-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,8 +1,177 @@
1
- import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
2
- import { dirname, extname, join, posix, relative, resolve, sep } from "node:path";
1
+ import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
3
3
  import ts from "typescript";
4
4
  import { Buffer } from "node:buffer";
5
5
  import { GenMapping, addMapping, toEncodedMap } from "@jridgewell/gen-mapping";
6
+ //#region lib/types/build-output-guard.js
7
+ /**
8
+ * Detection of workspace build outputs loaded into an analyzed face program,
9
+ * and of workspace imports that resolve to nothing. Typert reads workspace
10
+ * packages only from source when a caller rejects build outputs, so its
11
+ * result cannot depend on whether, or when, package compilation ran: an
12
+ * import that reaches `lib/` on a compiled tree resolves to nothing on a
13
+ * clean one, and both are rejected.
14
+ * @module @deepseek-ai/dsh-typert-generator/build-output-guard
15
+ */
16
+ /**
17
+ * List the program files that a workspace package holds under its `lib/`
18
+ * directory. A package is the nearest directory with a `package.json` at or
19
+ * below `root`; files under `node_modules` are external. Importers are found
20
+ * by resolving each program file's import specifiers again, so this pass
21
+ * costs extra only when an output is present.
22
+ * @param program - the analyzed face program.
23
+ * @param root - workspace root.
24
+ * @param host - module resolution host of the program.
25
+ * @returns loads in file-path order; empty when every workspace file comes from source.
26
+ */
27
+ function findBuildOutputLoads(program, root, host) {
28
+ const classifier = new BuildOutputClassifier(root);
29
+ const outputs = /* @__PURE__ */ new Map();
30
+ for (const sourceFile of program.getSourceFiles()) if (classifier.isBuildOutput(sourceFile.fileName)) outputs.set(classifier.canonical(sourceFile.fileName), []);
31
+ if (outputs.size === 0) return [];
32
+ const options = program.getCompilerOptions();
33
+ for (const sourceFile of program.getSourceFiles()) {
34
+ if (!classifier.isWorkspaceFile(sourceFile.fileName) || classifier.isBuildOutput(sourceFile.fileName)) continue;
35
+ for (const { fileName: specifier } of ts.preProcessFile(sourceFile.text, true, true).importedFiles) {
36
+ const resolved = ts.resolveModuleName(specifier, sourceFile.fileName, options, host).resolvedModule;
37
+ (resolved === void 0 ? void 0 : outputs.get(classifier.canonical(resolved.resolvedFileName)))?.push({
38
+ file: classifier.display(sourceFile.fileName),
39
+ specifier
40
+ });
41
+ }
42
+ }
43
+ return [...outputs].sort(([left], [right]) => left.localeCompare(right)).map(([file, importers]) => ({
44
+ file: classifier.display(file),
45
+ importers,
46
+ source: classifier.sourceFor(file)
47
+ }));
48
+ }
49
+ /**
50
+ * List the imports in workspace program files that name a workspace package
51
+ * and resolve to nothing. A workspace package is one whose `node_modules` link,
52
+ * found from the importing file's directories, leads inside `root` outside any
53
+ * `node_modules`; without that link or a `paths` alias the package cannot be
54
+ * identified, and type checking reports the import. The source of a subpath is
55
+ * derived from the package's `exports` `types` condition under `lib/types/`.
56
+ * @param program - the analyzed face program.
57
+ * @param root - workspace root.
58
+ * @param host - module resolution host of the program.
59
+ * @returns unresolved imports in file and specifier order.
60
+ */
61
+ function findUnresolvedWorkspaceImports(program, root, host) {
62
+ const classifier = new BuildOutputClassifier(root);
63
+ const options = program.getCompilerOptions();
64
+ const cache = ts.createModuleResolutionCache(root, (file) => file, options);
65
+ const found = [];
66
+ for (const sourceFile of program.getSourceFiles()) {
67
+ if (!classifier.isWorkspaceFile(sourceFile.fileName) || classifier.isBuildOutput(sourceFile.fileName)) continue;
68
+ for (const { fileName: specifier } of ts.preProcessFile(sourceFile.text, true, true).importedFiles) {
69
+ if (specifier.startsWith(".") || isAbsolute(specifier) || specifier.includes(":")) continue;
70
+ if (ts.resolveModuleName(specifier, sourceFile.fileName, options, host, cache).resolvedModule !== void 0) continue;
71
+ const packageRoot = classifier.linkedWorkspacePackage(sourceFile.fileName, packageNameOf(specifier));
72
+ if (packageRoot === void 0) continue;
73
+ const source = exportSource(packageRoot, specifier);
74
+ found.push({
75
+ file: classifier.display(sourceFile.fileName),
76
+ specifier,
77
+ source: source === void 0 ? void 0 : classifier.display(source)
78
+ });
79
+ }
80
+ }
81
+ return found.sort((left, right) => left.file.localeCompare(right.file) || left.specifier.localeCompare(right.specifier));
82
+ }
83
+ function packageNameOf(specifier) {
84
+ return specifier.split("/").slice(0, specifier.startsWith("@") ? 2 : 1).join("/");
85
+ }
86
+ function exportSource(packageRoot, specifier) {
87
+ const packageName = packageNameOf(specifier);
88
+ const subpath = specifier === packageName ? "." : `.${specifier.slice(packageName.length)}`;
89
+ let manifest;
90
+ try {
91
+ manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
92
+ } catch (_unreadableManifest) {
93
+ return;
94
+ }
95
+ const entry = property(property(manifest, "exports"), subpath);
96
+ const types = typeof entry === "string" ? entry : property(entry, "types");
97
+ if (typeof types !== "string" || !types.startsWith("./lib/types/")) return void 0;
98
+ const stem = join(packageRoot, "src", types.slice(12).replace(/\.d\.([cm]?)ts$/u, ".$1ts"));
99
+ return [stem, `${stem}x`].find((candidate) => existsSync(candidate));
100
+ }
101
+ function property(value, key) {
102
+ return typeof value === "object" && value !== null ? value[key] : void 0;
103
+ }
104
+ /**
105
+ * Classifies program files by their real paths, because module resolution
106
+ * reports symbolic-link targets and Windows long names while the workspace
107
+ * root may be spelled through a link or an 8.3 short name; reported paths are
108
+ * spelled under the caller's root.
109
+ */
110
+ var BuildOutputClassifier = class {
111
+ root;
112
+ packageRoots = /* @__PURE__ */ new Map();
113
+ realPaths = /* @__PURE__ */ new Map();
114
+ realRoot;
115
+ constructor(root) {
116
+ this.root = root;
117
+ this.realRoot = this.canonical(root);
118
+ }
119
+ canonical(file) {
120
+ const absolute = resolve(file);
121
+ const cached = this.realPaths.get(absolute);
122
+ if (cached !== void 0) return cached;
123
+ let real = absolute;
124
+ try {
125
+ real = realpathSync.native(absolute);
126
+ } catch (_missingPath) {}
127
+ this.realPaths.set(absolute, real);
128
+ return real;
129
+ }
130
+ display(file) {
131
+ const path = relative(this.realRoot, this.canonical(file));
132
+ return path.startsWith("..") || isAbsolute(path) ? resolve(file) : join(this.root, path);
133
+ }
134
+ isWorkspaceFile(file) {
135
+ const path = relative(this.realRoot, this.canonical(file));
136
+ return path !== "" && !path.startsWith("..") && !isAbsolute(path) && !path.split(sep).includes("node_modules");
137
+ }
138
+ isBuildOutput(file) {
139
+ if (!this.isWorkspaceFile(file)) return false;
140
+ const real = this.canonical(file);
141
+ const packageRoot = this.packageRoot(dirname(real));
142
+ return packageRoot !== void 0 && relative(packageRoot, real).split(sep)[0] === "lib";
143
+ }
144
+ sourceFor(file) {
145
+ const real = this.canonical(file);
146
+ const packageRoot = this.packageRoot(dirname(real));
147
+ if (packageRoot === void 0) return void 0;
148
+ const [lib, types, ...rest] = relative(packageRoot, real).split(sep);
149
+ const last = rest.at(-1);
150
+ if (lib !== "lib" || types !== "types" || last === void 0) return void 0;
151
+ const stem = join(packageRoot, "src", ...rest.slice(0, -1), last.replace(/\.d\.([cm]?)ts$/u, ".$1ts"));
152
+ const source = [stem, `${stem}x`].find((candidate) => existsSync(candidate));
153
+ return source === void 0 ? void 0 : this.display(source);
154
+ }
155
+ linkedWorkspacePackage(importer, packageName) {
156
+ let directory = dirname(resolve(importer));
157
+ for (;;) {
158
+ const link = join(directory, "node_modules", packageName);
159
+ if (existsSync(link)) {
160
+ const target = this.canonical(link);
161
+ return this.isWorkspaceFile(join(target, "package.json")) ? target : void 0;
162
+ }
163
+ if (this.canonical(directory) === this.realRoot || dirname(directory) === directory) return void 0;
164
+ directory = dirname(directory);
165
+ }
166
+ }
167
+ packageRoot(directory) {
168
+ if (this.packageRoots.has(directory)) return this.packageRoots.get(directory);
169
+ const found = existsSync(join(directory, "package.json")) ? directory : directory === this.realRoot || dirname(directory) === directory ? void 0 : this.packageRoot(dirname(directory));
170
+ this.packageRoots.set(directory, found);
171
+ return found;
172
+ }
173
+ };
174
+ //#endregion
6
175
  //#region lib/types/analyzer.js
7
176
  /**
8
177
  * TypeScript project analyzer for the compiler-independent Typert model.
@@ -47,6 +216,7 @@ var WorkspaceCaches = class {
47
216
  configs = /* @__PURE__ */ new Map();
48
217
  /** Registration inventories keyed by root and aggregate config paths. */
49
218
  registrations = /* @__PURE__ */ new Map();
219
+ aggregates = /* @__PURE__ */ new Map();
50
220
  hosts = /* @__PURE__ */ new Map();
51
221
  /**
52
222
  * Parse one tsconfig once per workspace snapshot.
@@ -62,6 +232,26 @@ var WorkspaceCaches = class {
62
232
  return parsed;
63
233
  }
64
234
  /**
235
+ * Read one face aggregate tsconfig once per workspace snapshot. An aggregate
236
+ * that declares `references` is parsed without expanding its `include`
237
+ * patterns, which affect neither its options nor its references.
238
+ * @param path - absolute aggregate config path.
239
+ * @returns the memoized compiler options and project references.
240
+ */
241
+ aggregate(path) {
242
+ let aggregate = this.aggregates.get(path);
243
+ if (aggregate === void 0) {
244
+ const { parsed } = parseConfig(path, true);
245
+ aggregate = {
246
+ path,
247
+ options: parsed.options,
248
+ projectReferences: parsed.projectReferences ?? []
249
+ };
250
+ this.aggregates.set(path, aggregate);
251
+ }
252
+ return aggregate;
253
+ }
254
+ /**
65
255
  * Return the shared compiler host for one face. Every program of one face
66
256
  * is built from the same aggregate compiler options (the first call wins),
67
257
  * so parsed source files, binder state, and module resolutions are safe to
@@ -121,6 +311,7 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
121
311
  faces: options.faces ?? ["host", "client"],
122
312
  checkDiagnostics: options.checkDiagnostics ?? true,
123
313
  mode: options.mode ?? "check",
314
+ rejectBuildOutputs: options.rejectBuildOutputs ?? false,
124
315
  ...options.packages === void 0 ? {} : { packages: options.packages }
125
316
  };
126
317
  this.caches = options.caches ?? new WorkspaceCaches();
@@ -140,10 +331,10 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
140
331
  if (registrations.length === 0) continue;
141
332
  if (this.options.checkDiagnostics) for (const registration of registrations) this.checkProject(registration);
142
333
  const aggregatePath = resolve(this.options.root, face === "host" ? this.options.hostConfig : this.options.clientConfig);
143
- const aggregate = this.caches.config(aggregatePath);
334
+ const aggregate = this.caches.aggregate(aggregatePath);
144
335
  const rootNames = [...new Set(registrations.flatMap((registration) => registration.config.parsed.fileNames))];
145
336
  const options = {
146
- ...aggregate.parsed.options,
337
+ ...aggregate.options,
147
338
  composite: false,
148
339
  incremental: false,
149
340
  noEmit: true
@@ -154,6 +345,7 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
154
345
  options,
155
346
  host
156
347
  });
348
+ if (this.options.rejectBuildOutputs) rejectBuildOutputLoads(face, this.options.root, aggregatePath, findBuildOutputLoads(program, this.options.root, host), findUnresolvedWorkspaceImports(program, this.options.root, host));
157
349
  faces.push(new FaceAnalyzer({
158
350
  root: this.options.root,
159
351
  face,
@@ -210,10 +402,11 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
210
402
  /**
211
403
  * Discover package faces from public-export-reachable Cordis augmentations
212
404
  * and explicit `@typert` roots without constructing a type-checker program.
405
+ * @param includeManifest - optional `package.json` predicate; a rejected package's sources and project config are not read.
213
406
  * @returns contributors grouped by package with deterministic face order.
214
407
  */
215
- discoverPackages() {
216
- const registrations = this.loadRegistrations().filter((registration) => this.options.faces.includes(registration.face)).filter((registration) => this.registrationHasSurface(registration));
408
+ discoverPackages(includeManifest) {
409
+ const registrations = this.loadRegistrations().filter((registration) => this.options.faces.includes(registration.face) && (includeManifest === void 0 || includeManifest(registration.manifest))).filter((registration) => this.registrationHasSurface(registration));
217
410
  const packages = /* @__PURE__ */ new Map();
218
411
  for (const registration of registrations) {
219
412
  const current = packages.get(registration.name) ?? {
@@ -272,36 +465,31 @@ var WorkspaceAnalyzer = class WorkspaceAnalyzer {
272
465
  for (const face of ["host", "client"]) {
273
466
  const aggregatePath = resolve(this.options.root, face === "host" ? this.options.hostConfig : this.options.clientConfig);
274
467
  if (!existsSync(aggregatePath)) continue;
275
- const aggregate = this.caches.config(aggregatePath);
276
- for (const reference of aggregate.parsed.projectReferences ?? []) {
468
+ const aggregate = this.caches.aggregate(aggregatePath);
469
+ for (const reference of aggregate.projectReferences) {
277
470
  const configPath = projectConfigPath(reference.path);
278
471
  const packageRoot = dirname(configPath);
279
472
  if (!isWithin(realPath(packageRoot), join(this.options.root, "packages")) && !(includeVendor && isWithin(realPath(packageRoot), join(this.options.root, "vendor")))) continue;
280
473
  const manifestPath = join(packageRoot, "package.json");
281
474
  if (!existsSync(manifestPath)) continue;
282
475
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
283
- if (typeof manifest.name !== "string") continue;
284
- const registration = {
285
- face,
286
- name: manifest.name,
287
- root: realPath(packageRoot),
288
- config: this.caches.config(configPath),
289
- manifest
290
- };
291
- if (!isDualFacePackage(manifest)) registrations.push(registration);
292
- else if (configPath === join(packageRoot, "tsconfig.json")) registrations.push({
293
- ...registration,
294
- face: "host",
295
- exportSubpaths: hostExportSubpaths(manifest)
296
- }, {
297
- ...registration,
298
- face: "client",
299
- exportSubpaths: clientExportSubpaths(manifest)
300
- });
301
- else registrations.push({
302
- ...registration,
303
- exportSubpaths: face === "host" ? hostExportSubpaths(manifest) : clientExportSubpaths(manifest)
476
+ const name = manifest.name;
477
+ if (typeof name !== "string") continue;
478
+ const root = realPath(packageRoot);
479
+ const caches = this.caches;
480
+ const register = (registrationFace, exportSubpaths) => ({
481
+ face: registrationFace,
482
+ name,
483
+ root,
484
+ get config() {
485
+ return caches.config(configPath);
486
+ },
487
+ manifest,
488
+ ...exportSubpaths === void 0 ? {} : { exportSubpaths }
304
489
  });
490
+ if (!isDualFacePackage(manifest)) registrations.push(register(face));
491
+ else if (configPath === join(packageRoot, "tsconfig.json")) registrations.push(register("host", hostExportSubpaths(manifest)), register("client", clientExportSubpaths(manifest)));
492
+ else registrations.push(register(face, face === "host" ? hostExportSubpaths(manifest) : clientExportSubpaths(manifest)));
305
493
  }
306
494
  }
307
495
  const inventory = uniqueBy(registrations, (registration) => `${registration.face}\0${registration.name}`).sort((left, right) => left.face.localeCompare(right.face) || left.name.localeCompare(right.name));
@@ -699,6 +887,7 @@ var FaceAnalyzer = class {
699
887
  if (hasModifier(method, ts.SyntaxKind.AbstractKeyword) || method.body === void 0) this.fail(method, "Remote methods must have a concrete implementation");
700
888
  if (!ts.isIdentifier(method.name)) this.fail(method, "Remote method names must be identifiers");
701
889
  if ((method.typeParameters?.length ?? 0) > 0) this.fail(method, "generic Remote methods are not supported");
890
+ const documentation = documentationOf(method);
702
891
  const methodName = method.name.text;
703
892
  const exportedMethod = invocation.exportName ?? methodName;
704
893
  const lookups = this.lookupDeclarations();
@@ -747,7 +936,11 @@ var FaceAnalyzer = class {
747
936
  }
748
937
  if (wires.has(modeled.wire)) this.fail(parameter, `duplicate Remote wire field ${modeled.wire}`);
749
938
  wires.add(modeled.wire);
750
- parameters.push(modeled);
939
+ const description = documentation.tags.find((tag) => tag.name === "param" && tag.argument === modeled.name)?.comment?.replace(/^[-—–]\s*/, "");
940
+ parameters.push({
941
+ ...modeled,
942
+ ...description === void 0 ? {} : { description }
943
+ });
751
944
  }
752
945
  let receiver = { kind: "direct" };
753
946
  if (invocation.kind === "context") {
@@ -780,6 +973,7 @@ var FaceAnalyzer = class {
780
973
  const { result: resultType, uplink: uplinkType } = this.remoteResultType(method, mode);
781
974
  const uplink = uplinkType === void 0 ? void 0 : { boundary: this.remoteBoundary(uplinkType, `${registration.name}#${binding.namespace}/${exportedMethod}:uplink`, false, "undefined") };
782
975
  return {
976
+ ...documentation,
783
977
  id: `${registration.name}#${binding.namespace}/${exportedMethod}`,
784
978
  service: binding.service,
785
979
  namespace: binding.namespace,
@@ -866,28 +1060,27 @@ var FaceAnalyzer = class {
866
1060
  kind: "direct",
867
1061
  exportName
868
1062
  };
869
- } else {
870
- if (!ts.isObjectLiteralExpression(argument) || argument.properties.length !== 1) this.fail(argument, "Remote() options must contain exactly mode: \"stream\"");
871
- const [property] = argument.properties;
872
- if (property === void 0) this.fail(argument, "Remote() options must contain exactly mode: \"stream\"");
873
- const mode = ts.isPropertyAssignment(property) && memberName(property.name) === "mode" ? stringLiteralValue(property.initializer) : void 0;
874
- if (mode !== "stream") this.fail(property, "Remote() options must contain exactly mode: \"stream\"");
875
- marker = {
876
- kind: "direct",
877
- mode
878
- };
879
- }
1063
+ } else marker = {
1064
+ kind: "direct",
1065
+ ...this.remoteOptions(argument, "Remote")
1066
+ };
880
1067
  } else if (ts.isCallExpression(expression) && this.isTypeMetaSymbol(expression.expression, "RemoteScope")) {
881
- if (expression.arguments.length < 1 || expression.arguments.length > 2) this.fail(expression, "RemoteScope() requires a Context key and optional exported method name");
1068
+ if (expression.arguments.length < 1 || expression.arguments.length > 2) this.fail(expression, "RemoteScope() requires a Context key and an optional exported method name or options object");
882
1069
  const context = stringLiteralValue(expression.arguments[0]);
883
1070
  if (context === void 0 || !isRemoteSegment(context)) this.fail(expression.arguments[0] ?? expression, "RemoteScope() key must be a string literal containing only RPC endpoint segment characters");
884
- const exportArgument = expression.arguments[1];
885
- const exportName = exportArgument === void 0 ? void 0 : stringLiteralValue(exportArgument);
886
- if (exportArgument !== void 0 && (exportName === void 0 || !isRemoteSegment(exportName))) this.fail(exportArgument, "RemoteScope() name must be a string literal containing only RPC endpoint segment characters");
887
- marker = {
1071
+ const option = expression.arguments[1];
1072
+ const exportName = option === void 0 ? void 0 : stringLiteralValue(option);
1073
+ if (option === void 0 || exportName !== void 0) {
1074
+ if (exportName !== void 0 && !isRemoteSegment(exportName)) this.fail(option ?? expression, "RemoteScope() name must be a string literal containing only RPC endpoint segment characters");
1075
+ marker = {
1076
+ kind: "context",
1077
+ context,
1078
+ ...exportName === void 0 ? {} : { exportName }
1079
+ };
1080
+ } else marker = {
888
1081
  kind: "context",
889
1082
  context,
890
- ...exportName === void 0 ? {} : { exportName }
1083
+ ...this.remoteOptions(option, "RemoteScope")
891
1084
  };
892
1085
  } else continue;
893
1086
  if (found !== void 0) this.fail(decorator, "a method can have only one Remote invocation decorator");
@@ -896,6 +1089,34 @@ var FaceAnalyzer = class {
896
1089
  return found;
897
1090
  }
898
1091
  /**
1092
+ * Read the options object of `@Remote` or `@RemoteScope`. `resources`
1093
+ * accepts any expression: its readers run only at runtime, where the
1094
+ * Gateway reads them from the prototype descriptor, so generation records
1095
+ * nothing for them.
1096
+ */
1097
+ remoteOptions(argument, decorator) {
1098
+ const requirement = decorator === "Remote" ? "Remote() options require name, mode: \"stream\", or resources" : "RemoteScope() options require name or resources";
1099
+ if (!ts.isObjectLiteralExpression(argument) || argument.properties.length === 0) this.fail(argument, requirement);
1100
+ let options = {};
1101
+ for (const property of argument.properties) {
1102
+ if (!ts.isPropertyAssignment(property)) this.fail(property, `${decorator}() options must use named values`);
1103
+ const name = memberName(property.name);
1104
+ if (decorator === "Remote" && name === "mode" && stringLiteralValue(property.initializer) === "stream") options = {
1105
+ ...options,
1106
+ mode: "stream"
1107
+ };
1108
+ else if (name === "name") {
1109
+ const value = stringLiteralValue(property.initializer);
1110
+ if (value === void 0 || !isRemoteSegment(value)) this.fail(property, `${decorator}() name must be an endpoint segment`);
1111
+ options = {
1112
+ ...options,
1113
+ exportName: value
1114
+ };
1115
+ } else if (name !== "resources") this.fail(property, requirement);
1116
+ }
1117
+ return options;
1118
+ }
1119
+ /**
899
1120
  * The item types a Remote method's authored return type declares. Unary
900
1121
  * methods unwrap `Promise<T>`; stream methods unwrap `Iterable<Out>`,
901
1122
  * `AsyncIterable<Out>`, or the protocol's `RemoteStream<Out, In>`, whose
@@ -1064,6 +1285,13 @@ var FaceAnalyzer = class {
1064
1285
  completed.set(type, id);
1065
1286
  return id;
1066
1287
  };
1288
+ const alias = type.aliasSymbol;
1289
+ const aliasDeclaration = alias === void 0 ? void 0 : preferredDeclaration(this.resolveSymbol(alias));
1290
+ if (alias?.name === "JsonValue" && aliasDeclaration !== void 0 && this.registrationForFile(aliasDeclaration.getSourceFile().fileName)?.name === "@deepseek-ai/dsh-util-values") return add({
1291
+ kind: "keyword",
1292
+ name: "unknown",
1293
+ json: true
1294
+ });
1067
1295
  const flags = type.flags;
1068
1296
  if (this.isRemoteByteArray(type)) return add({
1069
1297
  kind: "reference",
@@ -1152,8 +1380,19 @@ var FaceAnalyzer = class {
1152
1380
  text: String(value)
1153
1381
  });
1154
1382
  }
1155
- if (type.isUnionOrIntersection()) return add({
1156
- kind: (flags & ts.TypeFlags.Union) !== 0 ? "union" : "intersection",
1383
+ if (type.isIntersection()) {
1384
+ const material = type.types.filter((member) => !this.isRemotePhantomConstraint(member));
1385
+ if (material.length === 1) {
1386
+ const projected = convert(material[0]);
1387
+ const { id: _projectedId, ...value } = this.nodes.get(projected);
1388
+ return add(value);
1389
+ }
1390
+ if (!material.every((member) => (member.flags & ts.TypeFlags.Object) !== 0 && !this.checker.isArrayType(member) && !this.checker.isTupleType(member))) return add({
1391
+ kind: "intersection",
1392
+ types: material.map(convert)
1393
+ });
1394
+ } else if (type.isUnion()) return add({
1395
+ kind: "union",
1157
1396
  types: type.types.map(convert)
1158
1397
  });
1159
1398
  if ((flags & ts.TypeFlags.TypeParameter) !== 0) this.fail(authoredType, "Remote codec contains an unresolved type parameter");
@@ -1191,7 +1430,7 @@ var FaceAnalyzer = class {
1191
1430
  const propertyType = this.checker.getTypeOfSymbolAtLocation(property, declaration ?? authoredType);
1192
1431
  const symbolKey = property.getName();
1193
1432
  members.push({
1194
- ...EMPTY_DOCUMENTATION,
1433
+ ...declaration === void 0 ? EMPTY_DOCUMENTATION : documentationOf(declaration),
1195
1434
  id: `${id}#${symbolKey}`,
1196
1435
  name: symbolKey,
1197
1436
  ...symbolKey.startsWith("__@") ? { computed: "symbol" } : {},
@@ -2031,11 +2270,20 @@ function mergeWorkspaceModels(models) {
2031
2270
  crossFaceLinks: [...links.values()].sort(compareCrossFaceLinks)
2032
2271
  };
2033
2272
  }
2034
- function parseConfig(path) {
2273
+ /** {@link ts.sys} config reads without directory listing, so `include` patterns match no files. */
2274
+ const UNLISTED_CONFIG_HOST = {
2275
+ useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
2276
+ readDirectory: () => [],
2277
+ fileExists: (file) => ts.sys.fileExists(file),
2278
+ readFile: (file) => ts.sys.readFile(file)
2279
+ };
2280
+ function parseConfig(path, aggregate = false) {
2035
2281
  const compilerPath = path.split(sep).join("/");
2036
2282
  const read = ts.readConfigFile(compilerPath, (file) => ts.sys.readFile(file));
2037
2283
  if (read.error !== void 0) throw new TypertAnalysisError(formatDiagnostic(read.error));
2038
- const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(compilerPath), void 0, compilerPath);
2284
+ const raw = read.config;
2285
+ const host = aggregate && typeof raw === "object" && raw !== null && Object.hasOwn(raw, "references") ? UNLISTED_CONFIG_HOST : ts.sys;
2286
+ const parsed = ts.parseJsonConfigFileContent(read.config, host, dirname(compilerPath), void 0, compilerPath);
2039
2287
  if (parsed.errors.length > 0) throw new TypertAnalysisError(parsed.errors.map(formatDiagnostic).join("\n"));
2040
2288
  return {
2041
2289
  path,
@@ -2417,6 +2665,33 @@ function isWithin(path, root) {
2417
2665
  function slash(value) {
2418
2666
  return value.replaceAll("\\", "/");
2419
2667
  }
2668
+ function rejectBuildOutputLoads(face, root, aggregatePath, loads, unresolved) {
2669
+ if (loads.length === 0 && unresolved.length === 0) return;
2670
+ const path = (file) => slash(relative(root, file));
2671
+ const config = path(aggregatePath);
2672
+ const lines = [];
2673
+ const addAlias = (specifier, source) => {
2674
+ lines.push(` add ${JSON.stringify(specifier)}: [${JSON.stringify(`./${path(source)}`)}] to the compilerOptions.paths of ${config} or the config it extends`);
2675
+ };
2676
+ if (unresolved.length > 0) {
2677
+ lines.push(`typert(${face}): the ${face} program cannot resolve workspace imports, which would analyze as unknown types; map each import below to source:`);
2678
+ for (const entry of unresolved) {
2679
+ lines.push(` '${entry.specifier}' imported by ${path(entry.file)}`);
2680
+ if (entry.source === void 0) lines.push(` add a compilerOptions.paths alias for it to ${config} or the config it extends`);
2681
+ else addAlias(entry.specifier, entry.source);
2682
+ }
2683
+ }
2684
+ if (loads.length > 0) lines.push(`typert(${face}): the ${face} program loads workspace build outputs instead of source; resolve each import below to source so that generation does not depend on package compilation:`);
2685
+ for (const load of loads) {
2686
+ lines.push(` ${path(load.file)}`);
2687
+ if (load.importers.length === 0) lines.push(" no program source imports it directly; another build output or a reference directive loads it");
2688
+ for (const importer of load.importers) lines.push(` imported as '${importer.specifier}' by ${path(importer.file)}`);
2689
+ const specifiers = [...new Set(load.importers.map((importer) => importer.specifier))];
2690
+ if (load.source === void 0) lines.push(" no workspace source compiles to it; import a source module instead");
2691
+ else for (const specifier of specifiers) addAlias(specifier, load.source);
2692
+ }
2693
+ throw new TypertAnalysisError(lines.join("\n"));
2694
+ }
2420
2695
  function uniqueBy(values, key) {
2421
2696
  const result = /* @__PURE__ */ new Map();
2422
2697
  for (const value of values) if (!result.has(key(value))) result.set(key(value), value);
@@ -2772,6 +3047,148 @@ function assertNever(value) {
2772
3047
  throw new TypeGraphRenderError(`unsupported model variant ${JSON.stringify(value)}`);
2773
3048
  }
2774
3049
  //#endregion
3050
+ //#region lib/types/tool-schema.js
3051
+ /** Lossless projection into the harness tool-schema subset. */
3052
+ /** A resolved codec whose type the tool-schema subset cannot represent without weakening constraints. */
3053
+ var ToolSchemaError = class extends Error {
3054
+ name = "ToolSchemaError";
3055
+ };
3056
+ /**
3057
+ * Project one resolved type into the tool-schema subset that `@deepseek-ai/dsh-tools`
3058
+ * enforces on tool parameters, tool output, and the generated PTC SDK. A type the
3059
+ * subset cannot express without weakening constraints raises {@link ToolSchemaError},
3060
+ * which callers report as an absent projection rather than a narrower schema.
3061
+ * @param renderer - complete analyzed type graph.
3062
+ * @param type - resolved wire type.
3063
+ * @param closedObjects - close declared request objects with `additionalProperties: false`.
3064
+ * @param optional - omit a top-level undefined alternative supplied by an optional argument.
3065
+ * @returns an acyclic schema in the tool-schema subset.
3066
+ */
3067
+ function projectToolSchema(renderer, type, closedObjects = false, optional = false) {
3068
+ const active = /* @__PURE__ */ new Set();
3069
+ const fail = (subject) => {
3070
+ throw new ToolSchemaError(`typert tool schema: ${subject}`);
3071
+ };
3072
+ const project = (id, optional = false) => {
3073
+ const node = renderer.node(id);
3074
+ if (node.json) return {};
3075
+ if (active.has(id)) return fail("recursive selected type requires a supported JSON value declaration");
3076
+ active.add(id);
3077
+ try {
3078
+ switch (node.kind) {
3079
+ case "keyword":
3080
+ if ([
3081
+ "string",
3082
+ "number",
3083
+ "boolean"
3084
+ ].includes(node.name)) return { type: node.name };
3085
+ return fail(`unsupported ${node.name} value`);
3086
+ case "literal":
3087
+ if (typeof node.value === "bigint") return fail("bigint is not JSON");
3088
+ return {
3089
+ type: node.value === null ? "null" : typeof node.value,
3090
+ const: node.value
3091
+ };
3092
+ case "parenthesized": return project(node.type, optional);
3093
+ case "array": return {
3094
+ type: "array",
3095
+ items: project(node.element)
3096
+ };
3097
+ case "object": return object(node.members);
3098
+ case "reference": {
3099
+ if (node.target.kind !== "declaration" || node.arguments.length !== 0) return fail(`unsupported selected reference ${node.name}`);
3100
+ const declaration = renderer.declaration(node.target.symbol);
3101
+ const schema = declaration.kind === "alias" && declaration.type !== void 0 ? project(declaration.type, optional) : object(declaration.members);
3102
+ if (declaration.extends.length > 0) return fail("unresolved inherited selected type");
3103
+ return declaration.description === void 0 ? schema : {
3104
+ ...schema,
3105
+ description: declaration.description
3106
+ };
3107
+ }
3108
+ case "union": return union(node.types.filter((item) => {
3109
+ const branch = renderer.node(item);
3110
+ return !(optional && branch.kind === "keyword" && branch.name === "undefined");
3111
+ }).map((item) => project(item)));
3112
+ default: return fail(`unsupported selected ${node.kind} type`);
3113
+ }
3114
+ } finally {
3115
+ active.delete(id);
3116
+ }
3117
+ };
3118
+ const object = (members) => {
3119
+ const properties = {};
3120
+ const required = [];
3121
+ let indexed = false;
3122
+ for (const member of members) {
3123
+ if (member.static || member.visibility !== "public" || member.computed === "symbol") continue;
3124
+ if (member.kind === "index") {
3125
+ const key = member.signature.parameters[0];
3126
+ if (key === void 0 || project(key.type).type !== "string" || Object.keys(project(member.signature.returns)).length !== 0) return fail("selected dictionaries require string keys and unconstrained JSON values");
3127
+ indexed = true;
3128
+ continue;
3129
+ }
3130
+ if (member.kind !== "property" || member.computed === "dynamic") return fail(`unsupported member ${member.name}`);
3131
+ const name = member.jsonName ?? member.name;
3132
+ const schema = project(member.type, member.optional);
3133
+ properties[name] = member.description === void 0 ? schema : {
3134
+ ...schema,
3135
+ description: member.description
3136
+ };
3137
+ if (!member.optional) required.push(name);
3138
+ }
3139
+ return {
3140
+ type: "object",
3141
+ properties,
3142
+ required,
3143
+ additionalProperties: indexed || !closedObjects
3144
+ };
3145
+ };
3146
+ const union = (input) => {
3147
+ if (input.some((branch) => Object.keys(branch).length === 0)) return {};
3148
+ const branches = [];
3149
+ for (const branch of input) {
3150
+ const previous = branch.type !== void 0 && !["object", "array"].includes(branch.type) ? branches.find((candidate) => candidate.type === branch.type) : void 0;
3151
+ if (previous === void 0) {
3152
+ branches.push(branch);
3153
+ continue;
3154
+ }
3155
+ const values = literals(previous);
3156
+ const next = literals(branch);
3157
+ if (values === void 0 || next === void 0) {
3158
+ delete previous.const;
3159
+ delete previous.enum;
3160
+ } else {
3161
+ delete previous.const;
3162
+ previous.enum = [...new Set([...values, ...next])];
3163
+ }
3164
+ }
3165
+ if (branches.length === 1) return branches[0];
3166
+ if (branches.length === 0) return fail("empty selected union");
3167
+ for (const [index, left] of branches.entries()) for (const right of branches.slice(index + 1)) if (!disjoint(left, right)) return fail("selected union branches overlap or have no required discriminator");
3168
+ return { oneOf: branches };
3169
+ };
3170
+ return project(type, optional);
3171
+ }
3172
+ function literals(schema) {
3173
+ if (schema.const !== void 0) return [schema.const];
3174
+ return schema.enum;
3175
+ }
3176
+ function disjoint(left, right) {
3177
+ if (left.oneOf !== void 0) return left.oneOf.every((branch) => disjoint(branch, right));
3178
+ if (right.oneOf !== void 0) return right.oneOf.every((branch) => disjoint(left, branch));
3179
+ if (left.type !== right.type) return left.type !== void 0 && right.type !== void 0;
3180
+ if (left.type !== "object" || right.type !== "object") return false;
3181
+ return (left.required ?? []).some((name) => {
3182
+ if (!right.required?.includes(name)) return false;
3183
+ const a = left.properties?.[name];
3184
+ const b = right.properties?.[name];
3185
+ if (a === void 0 || b === void 0) return false;
3186
+ const first = literals(a);
3187
+ const second = literals(b);
3188
+ return first !== void 0 && second !== void 0 && first.every((value) => !second.includes(value));
3189
+ });
3190
+ }
3191
+ //#endregion
2775
3192
  //#region lib/types/emitter.js
2776
3193
  /**
2777
3194
  * Model-driven Typert artifact emitter. It consumes only FaceModel and
@@ -2802,7 +3219,7 @@ var FaceModelEmitter = class {
2802
3219
  emit(packageName) {
2803
3220
  const packageModel = this.face.packages.find((candidate) => candidate.name === packageName);
2804
3221
  if (packageModel === void 0) throw new TypertEmitError(`typert emitter(${this.face.face}): package ${packageName} is not modeled on this face`);
2805
- const schemaArtifact = new SchemaEmitter(this.renderer, packageModel.schemas, invocationBoundaryRoots(packageModel.invocations)).emit();
3222
+ const schemaArtifact = invocationSchemas(this.renderer, packageModel.schemas, invocationBoundaryRoots(packageModel.invocations));
2806
3223
  const runtimeModel = this.runtimeModel(packageModel);
2807
3224
  const js = this.renderJs(packageModel, schemaArtifact, runtimeModel);
2808
3225
  const dts = this.renderDts(packageModel, schemaArtifact);
@@ -2867,10 +3284,12 @@ var FaceModelEmitter = class {
2867
3284
  renderJs(packageModel, schemas, runtimeModel) {
2868
3285
  const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */"];
2869
3286
  if (schemas.definitions.length > 0) lines.push("import { z } from 'zod'", "");
3287
+ if (packageModel.invocations.length > 0) lines.push("import { createTypertRemoteInfo } from '@deepseek-ai/dsh-typert-protocol/remote-info'", "");
2870
3288
  lines.push(...schemas.definitions);
2871
3289
  if (schemas.definitions.length > 0) lines.push("");
2872
3290
  for (const schema of schemas.exports) lines.push(`export const ${schema.exportName} = ${schema.internalName}`);
2873
3291
  if (schemas.exports.length > 0) lines.push("");
3292
+ if (packageModel.invocations.length > 0) lines.push(`const $remote = ${this.remoteInfoLiteral(packageModel, schemas)}`, "");
2874
3293
  const model = JSON.stringify(runtimeModel, null, 2);
2875
3294
  lines.push("export const TYPERT = {");
2876
3295
  lines.push(` package: ${quote$1(packageModel.name)},`);
@@ -2878,9 +3297,8 @@ var FaceModelEmitter = class {
2878
3297
  lines.push(" schemas: [");
2879
3298
  for (const schema of schemas.exports) lines.push(` { name: ${quote$1(schema.exportName)}, create: ${schema.exportName} },`);
2880
3299
  lines.push(" ],");
2881
- lines.push(" invocations: [");
2882
- for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
2883
- lines.push(" ],");
3300
+ if (packageModel.invocations.length > 0) lines.push(" get invocations() { return $remote.descriptors },");
3301
+ else lines.push(" invocations: [", " ],");
2884
3302
  lines.push(` model: ${indent(model, 2).trimStart()},`);
2885
3303
  lines.push("}");
2886
3304
  return `${lines.join("\n")}\n`;
@@ -2903,32 +3321,47 @@ var FaceModelEmitter = class {
2903
3321
  return `${lines.join("\n")}\n`;
2904
3322
  }
2905
3323
  emitRemote(packageModel) {
2906
- const schemas = new SchemaEmitter(this.renderer, [], invocationBoundaryRoots(packageModel.invocations)).emit();
3324
+ const schemas = invocationSchemas(this.renderer, [], invocationBoundaryRoots(packageModel.invocations));
2907
3325
  const lines = ["/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */"];
2908
3326
  if (schemas.definitions.length > 0) lines.push("import { z } from 'zod'", "");
3327
+ lines.push("import { createTypertRemoteInfo } from '@deepseek-ai/dsh-typert-protocol/remote-info'", "");
2909
3328
  lines.push(...schemas.definitions);
2910
3329
  if (schemas.definitions.length > 0) lines.push("");
2911
- lines.push("export const TYPERT_REMOTE = {");
2912
- lines.push(` package: ${quote$1(packageModel.name)},`);
2913
- lines.push(" descriptors: [");
2914
- for (const invocation of packageModel.invocations) lines.push(`${indent(this.invocationLiteral(invocation, schemas), 4)},`);
2915
- lines.push(" ],");
2916
- lines.push("}");
2917
- lines.push("");
3330
+ lines.push(`export const TYPERT_REMOTE = ${this.remoteInfoLiteral(packageModel, schemas)}`, "");
2918
3331
  lines.push("export default TYPERT_REMOTE");
2919
- const declaration = this.renderRemoteDts(packageModel);
2920
3332
  return {
2921
3333
  js: `${lines.join("\n")}\n`,
2922
- ...declaration
3334
+ ...this.renderRemoteDts(packageModel)
2923
3335
  };
2924
3336
  }
3337
+ remoteMethodTypes(packageModel) {
3338
+ const names = allocateRemoteImportNames(remoteImports(packageModel.invocations));
3339
+ return `{\n${packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct").map((invocation) => {
3340
+ const input = this.remoteInputType(invocation, names);
3341
+ const result = this.renderer.renderType(invocation.result.type, names);
3342
+ const uplink = invocation.uplink === void 0 ? "" : `, ${this.renderer.renderType(invocation.uplink.boundary.type, names)}`;
3343
+ const mode = invocation.mode === "stream" ? `, ${quote$1("stream")}${uplink}` : containsBytes(this.renderer, invocation.result.codecType) ? `, 'unary', never, RemoteDecoded<${result}>` : "";
3344
+ return ` readonly ${quote$1(`${invocation.namespace}/${invocation.method}`)}: TypertRemoteMethod<${input}, ${result}${mode}>`;
3345
+ }).join("\n")}\n}`;
3346
+ }
3347
+ remoteInputType(invocation, names) {
3348
+ return `{ ${invocation.parameters.map((parameter) => `readonly ${quote$1(parameter.wire)}${parameter.boundary.acceptsUndefined ? "?" : ""}: ${this.renderer.renderType(parameter.boundary.type, names)}`).join("; ")} }`;
3349
+ }
3350
+ remoteInfoLiteral(packageModel, schemas) {
3351
+ return [
3352
+ "createTypertRemoteInfo({",
3353
+ ` package: ${quote$1(packageModel.name)},`,
3354
+ " methods: {",
3355
+ ...packageModel.invocations.map((invocation) => ` ${quote$1(`${invocation.namespace}/${invocation.method}`)}: ${indent(this.invocationLiteral(invocation, schemas), 4).trimStart()},`),
3356
+ " },",
3357
+ "}, z)"
3358
+ ].join("\n");
3359
+ }
2925
3360
  invocationLiteral(invocation, schemas) {
2926
3361
  const lines = [
2927
3362
  "{",
2928
- ` id: ${quote$1(invocation.id)},`,
2929
3363
  ` service: ${quote$1(invocation.service)},`,
2930
- ` namespace: ${quote$1(invocation.namespace)},`,
2931
- ` method: ${quote$1(invocation.method)},`
3364
+ ...invocation.description === void 0 ? [] : [` description: ${quote$1(invocation.description)},`]
2932
3365
  ];
2933
3366
  if (invocation.implementation !== void 0) lines.push(` implementation: ${quote$1(invocation.implementation)},`);
2934
3367
  if (invocation.mode !== void 0) lines.push(` mode: ${quote$1(invocation.mode)},`);
@@ -2951,11 +3384,12 @@ var FaceModelEmitter = class {
2951
3384
  invocation.parameters.forEach((parameter, index) => {
2952
3385
  lines.push(" {");
2953
3386
  lines.push(` name: ${quote$1(parameter.name)},`);
2954
- lines.push(` wire: ${quote$1(parameter.wire)},`);
3387
+ if (parameter.wire !== parameter.name) lines.push(` wire: ${quote$1(parameter.wire)},`);
3388
+ if (parameter.description !== void 0) lines.push(` description: ${quote$1(parameter.description)},`);
2955
3389
  lines.push(` source: ${quote$1(parameter.source)},`);
2956
3390
  if (parameter.lookup !== void 0) lines.push(` lookup: ${quote$1(parameter.lookup)},`);
2957
3391
  if (parameter.boundary.acceptsUndefined) lines.push(" acceptsUndefined: true,");
2958
- lines.push(` codec: ${indent(strictCodec(parameter.boundary, schemas.boundary(parameterBoundaryKey(invocation, index))), 6).trimStart()},`);
3392
+ for (const strict of [false, true]) lines.push(` ${strict ? "strictCodec" : "codec"}: ${indent(strictCodec(parameter.boundary, schemas.boundary(parameterBoundaryKey(invocation, index, strict)), void 0, toolProjection(() => projectToolSchema(this.renderer, parameter.boundary.codecType, strict, parameter.boundary.acceptsUndefined))), 6).trimStart()},`);
2959
3393
  lines.push(" },");
2960
3394
  });
2961
3395
  lines.push(" ],");
@@ -2965,7 +3399,8 @@ var FaceModelEmitter = class {
2965
3399
  lines.push(" },");
2966
3400
  }
2967
3401
  if (invocation.cancellation !== void 0) lines.push(" cancellation: { parameter: 'signal' },");
2968
- lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation)), schemas.encoder(resultBoundaryKey(invocation))), 2).trimStart()},`);
3402
+ const returns = invocation.tags.find((tag) => tag.name === "returns" || tag.name === "return")?.comment?.replace(/^[-—–]\s*/, "");
3403
+ lines.push(` result: ${indent(strictCodec(invocation.result, schemas.boundary(resultBoundaryKey(invocation)), schemas.encoder(resultBoundaryKey(invocation)), toolProjection(() => projectToolSchema(this.renderer, invocation.result.codecType)), returns), 2).trimStart()},`);
2969
3404
  lines.push(` sourceLocation: ${JSON.stringify(invocation.location)},`);
2970
3405
  lines.push("}");
2971
3406
  return lines.join("\n");
@@ -2985,9 +3420,11 @@ var FaceModelEmitter = class {
2985
3420
  const lines = [
2986
3421
  "/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */",
2987
3422
  "import type {",
3423
+ ...packageModel.invocations.some((invocation) => containsBytes(this.renderer, invocation.result.codecType)) ? [" RemoteDecoded,"] : [],
2988
3424
  " RemoteResult,",
2989
3425
  ...packageModel.invocations.some((invocation) => invocation.mode === "stream") ? [" RemoteStreamHandle,"] : [],
2990
- " TypertRemoteContribution,",
3426
+ " TypertRemoteInfo,",
3427
+ ...packageModel.invocations.some((invocation) => invocation.invocation.kind === "direct") ? [" TypertRemoteMethod,"] : [],
2991
3428
  "} from '@deepseek-ai/dsh-typert-protocol'"
2992
3429
  ];
2993
3430
  const sourceMap = new GenMapping({ file: "typert.remote-client.d.ts" });
@@ -2996,12 +3433,6 @@ var FaceModelEmitter = class {
2996
3433
  lines.push(`import type { ${names.join(", ")} } from ${quote$1(specifier)}`);
2997
3434
  }
2998
3435
  lines.push("");
2999
- if (packageModel.invocations.some((invocation) => containsBytes(this.renderer, invocation.result.codecType))) {
3000
- lines.push("type RemoteDecoded<Value> = Value extends Uint8Array ? Uint8Array<ArrayBuffer>");
3001
- lines.push(" : Value extends string | number | boolean | null | undefined ? Value");
3002
- lines.push(" : { [Key in keyof Value]: RemoteDecoded<Value[Key]> }");
3003
- lines.push("");
3004
- }
3005
3436
  lines.push("declare module '@deepseek-ai/dsh-typert-protocol' {");
3006
3437
  const direct = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "direct");
3007
3438
  const scoped = packageModel.invocations.filter((invocation) => invocation.invocation.kind === "context" || invocation.scope !== void 0);
@@ -3025,7 +3456,7 @@ var FaceModelEmitter = class {
3025
3456
  }
3026
3457
  lines.push("}");
3027
3458
  lines.push("");
3028
- lines.push("export declare const TYPERT_REMOTE: TypertRemoteContribution");
3459
+ lines.push(`export declare const TYPERT_REMOTE: TypertRemoteInfo<${this.remoteMethodTypes(packageModel)}>`);
3029
3460
  lines.push("export default TYPERT_REMOTE");
3030
3461
  lines.push("//# sourceMappingURL=typert.remote-client.d.ts.map");
3031
3462
  return {
@@ -3092,6 +3523,27 @@ function uniqueNamespaces(invocations) {
3092
3523
  function remoteNamespaceInterface(namespace) {
3093
3524
  return `TypertRemoteNamespace$${Buffer.from(namespace, "utf8").toString("hex")}`;
3094
3525
  }
3526
+ function toolProjection(project) {
3527
+ try {
3528
+ return `jsonSchema: ${JSON.stringify(project())}`;
3529
+ } catch (error) {
3530
+ if (!(error instanceof ToolSchemaError)) throw error;
3531
+ return `jsonSchemaError: ${quote$1(error.message)}`;
3532
+ }
3533
+ }
3534
+ function invocationSchemas(renderer, schemas, roots) {
3535
+ const identifiers = /* @__PURE__ */ new Set();
3536
+ const ordinary = new SchemaEmitter(renderer, schemas, roots.filter((root) => !root.closedObjects), {}, identifiers).emit();
3537
+ const closedRoots = roots.filter((root) => root.closedObjects);
3538
+ const closed = new SchemaEmitter(renderer, [], closedRoots, { closedObjects: true }, identifiers).emit();
3539
+ const keys = new Set(closedRoots.map((root) => root.key));
3540
+ return {
3541
+ definitions: [...ordinary.definitions, ...closed.definitions],
3542
+ exports: ordinary.exports,
3543
+ boundary: (key) => keys.has(key) ? closed.boundary(key) : ordinary.boundary(key),
3544
+ encoder: (key) => keys.has(key) ? closed.encoder(key) : ordinary.encoder(key)
3545
+ };
3546
+ }
3095
3547
  var ResultEncoderEmitter = class {
3096
3548
  renderer;
3097
3549
  names = /* @__PURE__ */ new Map();
@@ -3190,20 +3642,21 @@ var SchemaEmitter = class {
3190
3642
  renderer;
3191
3643
  schemas;
3192
3644
  boundaries;
3645
+ options;
3193
3646
  names = /* @__PURE__ */ new Map();
3194
3647
  boundaryNames = /* @__PURE__ */ new Map();
3195
3648
  declarations;
3196
- constructor(renderer, schemas, boundaries) {
3649
+ constructor(renderer, schemas, boundaries, options = {}, identifiers = /* @__PURE__ */ new Set()) {
3197
3650
  this.renderer = renderer;
3198
3651
  this.schemas = schemas;
3199
3652
  this.boundaries = boundaries;
3653
+ this.options = options;
3200
3654
  const declarations = /* @__PURE__ */ new Map();
3201
3655
  for (const schema of schemas) for (const declaration of renderer.declarationClosureForTypes([schema.type])) declarations.set(declaration.id, declaration);
3202
3656
  for (const boundary of boundaries) for (const declaration of renderer.declarationClosureForTypes([boundary.type])) declarations.set(declaration.id, declaration);
3203
3657
  this.declarations = renderer.graph.declarations.filter((declaration) => declarations.has(declaration.id));
3204
- const identifiers = /* @__PURE__ */ new Set();
3205
3658
  for (const declaration of this.declarations) {
3206
- const base = `${safeIdentifier(declaration.name)}$schema`;
3659
+ const base = `${options.closedObjects ? "closed$" : ""}${safeIdentifier(declaration.name)}$schema`;
3207
3660
  let name = base;
3208
3661
  let suffix = 2;
3209
3662
  while (identifiers.has(name)) name = `${base}${String(suffix++)}`;
@@ -3227,7 +3680,7 @@ var SchemaEmitter = class {
3227
3680
  const encode = encoder.reference(boundary.type);
3228
3681
  if (encode !== void 0) encoders.set(boundary.key, encode);
3229
3682
  const name = this.boundaryName(boundary.key);
3230
- definitions.push(`let ${name}$value\nconst ${name} = () => (${name}$value ??= ${this.typeSchema(boundary.type)})`);
3683
+ definitions.push(this.cachedFactory(name, this.typeSchema(boundary.type)));
3231
3684
  }
3232
3685
  definitions.push(...encoder.definitions());
3233
3686
  return {
@@ -3243,11 +3696,14 @@ var SchemaEmitter = class {
3243
3696
  }
3244
3697
  declarationDefinition(declaration) {
3245
3698
  const name = this.schemaName(declaration.id);
3246
- if (declaration.typeParameters.length === 0) return `let ${name}$value\nconst ${name} = () => (${name}$value ??= ${this.declarationSchema(declaration, /* @__PURE__ */ new Map())})`;
3699
+ if (declaration.typeParameters.length === 0) return this.cachedFactory(name, this.declarationSchema(declaration, /* @__PURE__ */ new Map()));
3247
3700
  const parameters = declaration.typeParameters.map((parameter, index) => [`type${String(index)}$schema`, parameter.id]);
3248
3701
  const substitutions = new Map(parameters.map(([schema, id]) => [id, schema]));
3249
3702
  return `const ${name} = (${parameters.map(([schema]) => schema).join(", ")}) => ${this.declarationSchema(declaration, substitutions)}`;
3250
3703
  }
3704
+ cachedFactory(name, expression) {
3705
+ return `let ${name}$value\nconst ${name} = () => (${name}$value ??= ${expression})`;
3706
+ }
3251
3707
  declarationSchema(declaration, substitutions) {
3252
3708
  if (declaration.kind === "enum") this.fail(declaration.name, "enum declarations have no Zod projection");
3253
3709
  if (declaration.kind === "alias") {
@@ -3260,6 +3716,7 @@ var SchemaEmitter = class {
3260
3716
  }
3261
3717
  typeSchema(id, substitutions = /* @__PURE__ */ new Map()) {
3262
3718
  const node = this.renderer.node(id);
3719
+ if (node.json) return "z.json()";
3263
3720
  switch (node.kind) {
3264
3721
  case "keyword": return this.keywordSchema(node.name);
3265
3722
  case "literal": return `z.literal(${node.text})`;
@@ -3357,6 +3814,8 @@ var SchemaEmitter = class {
3357
3814
  objectSchema(members, subject, substitutions) {
3358
3815
  const properties = [];
3359
3816
  const indices = [];
3817
+ let stringIndexValue;
3818
+ let stringIndexType;
3360
3819
  let symbolMembers = 0;
3361
3820
  for (const member of members) {
3362
3821
  if (member.static || member.visibility !== "public") continue;
@@ -3368,22 +3827,53 @@ var SchemaEmitter = class {
3368
3827
  if (member.kind === "index") {
3369
3828
  const parameter = member.signature.parameters[0];
3370
3829
  if (member.signature.parameters.length !== 1 || parameter === void 0) this.fail(subject, "index signature must have exactly one key parameter");
3371
- indices.push(this.readonly(`z.record(${this.typeSchema(parameter.type, substitutions)}, ${this.typeSchema(member.signature.returns, substitutions)})`, member.readonly));
3830
+ const key = this.typeSchema(parameter.type, substitutions);
3831
+ const value = this.typeSchema(member.signature.returns, substitutions);
3832
+ if (key === "z.string()") {
3833
+ stringIndexValue = value;
3834
+ stringIndexType = member.signature.returns;
3835
+ }
3836
+ indices.push(this.readonly(`z.record(${key}, ${value})`, member.readonly));
3372
3837
  continue;
3373
3838
  }
3374
3839
  if (member.kind !== "property") this.fail(subject, `${member.kind} member ${member.name} is not data-schema projectable`);
3375
- const schema = this.readonly(this.typeSchema(member.type, substitutions), member.readonly && !containsBytes(this.renderer, member.type, false));
3376
- const property = this.describe(this.optional(schema, member.optional), member);
3377
- properties.push(`${quote$1(member.jsonName ?? member.name)}: ${property}`);
3840
+ properties.push(member);
3378
3841
  }
3379
3842
  if (indices.length > 1) this.fail(subject, "object type has more than one JSON index signature");
3380
3843
  if (properties.length === 0 && indices.length === 0 && symbolMembers > 0) return "z.unknown()";
3381
- const object = `z.object({${properties.length === 0 ? "" : `\n${properties.map((property) => ` ${property},`).join("\n")}\n`}})`;
3844
+ const fields = properties.map((member) => {
3845
+ const type = this.options.closedObjects && stringIndexType !== void 0 ? this.indexedPropertySchema(member.type, stringIndexType, substitutions) : this.typeSchema(member.type, substitutions);
3846
+ const schema = this.readonly(type, member.readonly && !containsBytes(this.renderer, member.type, false));
3847
+ return `${quote$1(member.jsonName ?? member.name)}: ${this.describe(this.optional(schema, member.optional), member)}`;
3848
+ });
3849
+ const object = `z.${this.options.closedObjects && indices.length === 0 ? "strictObject" : "object"}({${fields.length === 0 ? "" : `\n${fields.map((field) => ` ${field},`).join("\n")}\n`}})`;
3382
3850
  const index = indices[0];
3383
3851
  if (index === void 0) return object;
3384
3852
  if (properties.length === 0) return index;
3853
+ if (this.options.closedObjects && stringIndexValue !== void 0) return `${object}.catchall(${stringIndexValue})`;
3385
3854
  return `z.intersection(${object}, ${index})`;
3386
3855
  }
3856
+ indexedPropertySchema(declared, indexed, substitutions) {
3857
+ if (declared === indexed) return this.typeSchema(declared, substitutions);
3858
+ const own = this.renderer.node(declared);
3859
+ const value = this.renderer.node(indexed);
3860
+ if (own.kind === "union") return `z.union([${own.types.map((type) => this.indexedPropertySchema(type, indexed, substitutions)).join(", ")}])`;
3861
+ if (value.kind === "union") return `z.union([${value.types.map((type) => this.indexedPropertySchema(declared, type, substitutions)).join(", ")}])`;
3862
+ const ownProperties = own.kind === "object" ? own.members.filter((member) => member.kind === "property") : [];
3863
+ const valueProperties = value.kind === "object" ? value.members.filter((member) => member.kind === "property") : [];
3864
+ if (own.kind === "object" && value.kind === "object" && !own.json && !value.json && ownProperties.length === own.members.length && valueProperties.length === value.members.length && [...ownProperties, ...valueProperties].every((member) => !member.static && member.visibility === "public" && member.computed === void 0)) {
3865
+ const indexedFields = new Map(valueProperties.map((member) => [member.jsonName ?? member.name, member]));
3866
+ const overlaps = ownProperties.flatMap((member) => {
3867
+ const inherited = indexedFields.get(member.jsonName ?? member.name);
3868
+ if (inherited === void 0) return [];
3869
+ const combined = this.indexedPropertySchema(member.type, inherited.type, substitutions);
3870
+ const schema = this.readonly(this.optional(combined, member.optional && inherited.optional), (member.readonly || inherited.readonly) && !containsBytes(this.renderer, member.type, false));
3871
+ return [`${quote$1(member.jsonName ?? member.name)}: ${this.describe(schema, member)}`];
3872
+ });
3873
+ return `z.strictObject({ ...${this.typeSchema(indexed, substitutions)}.shape, ...${this.typeSchema(declared, substitutions)}.shape${overlaps.length === 0 ? "" : `, ${overlaps.join(", ")}`} })`;
3874
+ }
3875
+ return `z.intersection(${this.typeSchema(declared, substitutions)}, ${this.typeSchema(indexed, substitutions)})`;
3876
+ }
3387
3877
  exportSchemaName(model) {
3388
3878
  const name = this.schemaName(model.symbol);
3389
3879
  if (this.renderer.declaration(model.symbol).typeParameters.length > 0) this.fail(model.export.name, "generic schema exports require a concrete declaration");
@@ -3451,6 +3941,11 @@ function invocationBoundaryRoots(invocations) {
3451
3941
  key: parameterBoundaryKey(invocation, index),
3452
3942
  type: parameter.boundary.codecType
3453
3943
  });
3944
+ result.push({
3945
+ key: parameterBoundaryKey(invocation, index, true),
3946
+ type: parameter.boundary.codecType,
3947
+ closedObjects: true
3948
+ });
3454
3949
  });
3455
3950
  if (invocation.uplink !== void 0) result.push({
3456
3951
  key: uplinkBoundaryKey(invocation),
@@ -3469,8 +3964,8 @@ function contextBoundaryKey(invocation) {
3469
3964
  function uplinkBoundaryKey(invocation) {
3470
3965
  return `${invocation.id}:uplink`;
3471
3966
  }
3472
- function parameterBoundaryKey(invocation, index) {
3473
- return `${invocation.id}:parameter:${String(index)}`;
3967
+ function parameterBoundaryKey(invocation, index, strict = false) {
3968
+ return `${invocation.id}:parameter:${String(index)}${strict ? ":strict" : ""}`;
3474
3969
  }
3475
3970
  function resultBoundaryKey(invocation) {
3476
3971
  return `${invocation.id}:result`;
@@ -3491,12 +3986,14 @@ function containsBytes(renderer, id, nested = true, visited = /* @__PURE__ */ ne
3491
3986
  if (node.kind === "object" && node.members.some((member) => containsBytes(renderer, member.kind === "property" ? member.type : member.signature.returns, nested, visited))) return true;
3492
3987
  return childTypeNodeIds(node).some((child) => containsBytes(renderer, child, nested, visited));
3493
3988
  }
3494
- function strictCodec(boundary, schema, encoder) {
3989
+ function strictCodec(boundary, schema, encoder, projection, description) {
3495
3990
  return [
3496
3991
  "{",
3497
3992
  " mode: 'strict',",
3498
3993
  ` typeSymbol: ${quote$1(boundary.typeSymbol)},`,
3499
3994
  ` create: ${schema},`,
3995
+ ...description === void 0 ? [] : [` description: ${quote$1(description)},`],
3996
+ ...projection === void 0 ? [] : [` ${projection},`],
3500
3997
  ...encoder === void 0 ? [] : [` decode: value => ${schema}().parse(value),`, ` encode: (value, writeBytes) => ${encoder}(value, writeBytes, [], new Map()),`],
3501
3998
  "}"
3502
3999
  ].join("\n");
@@ -3523,7 +4020,8 @@ function allocateRemoteImportNames(imports) {
3523
4020
  "RemoteDecoded",
3524
4021
  "RemoteResult",
3525
4022
  "RemoteStreamHandle",
3526
- "TypertRemoteContribution",
4023
+ "TypertRemoteInfo",
4024
+ "TypertRemoteMethod",
3527
4025
  "TYPERT_REMOTE"
3528
4026
  ]);
3529
4027
  const names = /* @__PURE__ */ new Map();
@@ -4291,34 +4789,38 @@ var WorkspaceTypertGenerator = class {
4291
4789
  * Find public package faces that contribute Cordis services/events or
4292
4790
  * explicitly tagged Typert roots.
4293
4791
  * @param faces - optional independent program faces to inspect.
4792
+ * @param includeManifest - optional `package.json` predicate; a rejected package's sources and project config are not read.
4294
4793
  * @returns discovered packages in stable package-name order.
4295
4794
  */
4296
- discover(faces) {
4795
+ discover(faces, includeManifest) {
4297
4796
  return new WorkspaceAnalyzer({
4298
4797
  root: this.root,
4299
4798
  caches: this.caches,
4300
4799
  ...faces === void 0 ? {} : { faces }
4301
- }).discoverPackages();
4800
+ }).discoverPackages(includeManifest);
4302
4801
  }
4303
4802
  /**
4304
4803
  * Generate all discovered contributors, or an explicit package subset.
4305
- * @param packages - optional exact package names for a focused pass.
4804
+ * @param packages - optional exact package names to emit; every pass includes the same face-wide declaration merges.
4306
4805
  * @param faces - optional independent program faces to analyze.
4307
4806
  * @returns one artifact per package face.
4308
4807
  */
4309
4808
  generate(packages, faces) {
4310
- const selected = packages ?? this.discover(faces).map((candidate) => candidate.package);
4809
+ const contributors = this.discover(faces).map((candidate) => candidate.package);
4810
+ const selected = new Set(packages ?? contributors);
4311
4811
  const workspace = new WorkspaceAnalyzer({
4312
4812
  root: this.root,
4313
- packages: selected,
4813
+ packages: contributors,
4314
4814
  caches: this.caches,
4315
4815
  ...faces === void 0 ? {} : { faces },
4316
- ...this.options.checkDiagnostics === void 0 ? {} : { checkDiagnostics: this.options.checkDiagnostics }
4816
+ ...this.options.checkDiagnostics === void 0 ? {} : { checkDiagnostics: this.options.checkDiagnostics },
4817
+ ...this.options.rejectBuildOutputs === void 0 ? {} : { rejectBuildOutputs: this.options.rejectBuildOutputs }
4317
4818
  }).analyze();
4318
4819
  const artifacts = [];
4319
4820
  for (const face of workspace.faces) {
4320
4821
  const emitter = new FaceModelEmitter(face);
4321
4822
  for (const packageModel of face.packages) {
4823
+ if (!selected.has(packageModel.name)) continue;
4322
4824
  const artifact = {
4323
4825
  ...emitter.emit(packageModel.name),
4324
4826
  packageRoot: packageModel.root
@@ -4329,6 +4831,16 @@ var WorkspaceTypertGenerator = class {
4329
4831
  }
4330
4832
  return artifacts;
4331
4833
  }
4834
+ /**
4835
+ * Generate every explicit contributor: a package whose manifest declares a
4836
+ * Typert artifact export and whose public graph has a Typert surface.
4837
+ * @param faces - optional independent program faces to analyze.
4838
+ * @returns one artifact per contributor face.
4839
+ */
4840
+ generateContributors(faces) {
4841
+ const packages = this.discover(faces, (manifest) => hasTypertExport(manifest.exports)).map((candidate) => candidate.package);
4842
+ return packages.length === 0 ? [] : this.generate(packages, faces);
4843
+ }
4332
4844
  validateExport(artifact) {
4333
4845
  const manifestPath = resolve(this.root, artifact.packageRoot, "package.json");
4334
4846
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
@@ -4355,10 +4867,90 @@ var WorkspaceTypertGenerator = class {
4355
4867
  for (const file of remoteFiles) if (!files.includes(file)) throw new TypertAnalysisError(`typert(host): ${artifact.package} package files must include ${file}`);
4356
4868
  }
4357
4869
  };
4870
+ /**
4871
+ * Find the workspace root that Typert generation runs against: the nearest
4872
+ * directory at or above `start` that holds both `tsconfig.host.json` and
4873
+ * `packages/`. The analyzer registers projects under `<root>/packages`, so a
4874
+ * package's own `tsconfig.host.json` face leaf does not qualify.
4875
+ * @param start - directory to search from, such as a bundle output or a working directory.
4876
+ * @returns the absolute workspace root.
4877
+ * @throws If no ancestor of `start` qualifies.
4878
+ */
4879
+ function findWorkspaceRoot(start) {
4880
+ let current = resolve(start);
4881
+ while (!existsSync(join(current, "tsconfig.host.json")) || !existsSync(join(current, "packages"))) {
4882
+ const parent = dirname(current);
4883
+ if (parent === current) throw new Error(`typert-generator: cannot find workspace root above ${start}`);
4884
+ current = parent;
4885
+ }
4886
+ return current;
4887
+ }
4888
+ /**
4889
+ * Whether a `package.json#exports` field declares a Typert artifact subpath
4890
+ * (`./typert`, `./client/typert`, or `./remote`).
4891
+ * @param exportsField - the manifest's `exports` value.
4892
+ * @returns true when the package opts in to Typert publication.
4893
+ */
4894
+ function hasTypertExport(exportsField) {
4895
+ if (exportsField === null || typeof exportsField !== "object" || Array.isArray(exportsField)) return false;
4896
+ return Object.hasOwn(exportsField, "./typert") || Object.hasOwn(exportsField, "./client/typert") || Object.hasOwn(exportsField, "./remote");
4897
+ }
4358
4898
  function sameExport(actual, expected) {
4359
4899
  if (actual === null || typeof actual !== "object" || Array.isArray(actual)) return false;
4360
4900
  const value = actual;
4361
4901
  return value.types === expected.types && value.default === expected.default;
4362
4902
  }
4363
4903
  //#endregion
4364
- export { CordisCatalogProjector, FaceModelEmitter, REGION_BEGIN, REGION_END, TypeGraphRenderError, TypeGraphRenderer, TypertAnalysisError, TypertEmitError, WorkspaceAnalyzer, WorkspaceCaches, WorkspaceTypertGenerator, collectEvents, collectServices, projectCordisCatalog, renderInheritedPage, renderPageRegion };
4904
+ //#region lib/types/artifact-files.js
4905
+ /**
4906
+ * Package-local files of generated Typert artifacts. The tsdown plugin and
4907
+ * standalone workspace generation both write through this module, so a pass
4908
+ * over the same artifacts leaves the same bytes on disk.
4909
+ * @module @deepseek-ai/dsh-typert-generator/artifact-files
4910
+ */
4911
+ const REMOTE_CLIENT_FILES = [
4912
+ "typert.remote-client.js",
4913
+ "typert.remote-client.d.ts",
4914
+ "typert.remote-client.d.ts.map"
4915
+ ];
4916
+ /**
4917
+ * Write one package's artifacts under `<packageDir>/lib`: `typert.<face>.js`
4918
+ * and `typert.<face>.d.ts` per artifact, plus the three
4919
+ * `typert.remote-client.*` files when an artifact carries Remote methods. A
4920
+ * Host artifact without Remote methods removes any Remote client files left
4921
+ * by an earlier pass. Files are written one by one, so a failed write can
4922
+ * leave this package's outputs partially replaced.
4923
+ * @param packageDir - absolute package root.
4924
+ * @param artifacts - artifacts of that package only.
4925
+ * @throws A file-system error when the output directory or a file cannot be written.
4926
+ */
4927
+ function writePackageArtifacts(packageDir, artifacts) {
4928
+ const output = join(packageDir, "lib");
4929
+ mkdirSync(output, { recursive: true });
4930
+ let emittedRemote = false;
4931
+ for (const artifact of artifacts) {
4932
+ writeFileSync(join(output, `typert.${artifact.face}.js`), artifact.js);
4933
+ writeFileSync(join(output, `typert.${artifact.face}.d.ts`), artifact.dts);
4934
+ if (artifact.remote !== void 0) {
4935
+ emittedRemote = true;
4936
+ writeFileSync(join(output, "typert.remote-client.js"), artifact.remote.js);
4937
+ writeFileSync(join(output, "typert.remote-client.d.ts"), artifact.remote.dts);
4938
+ writeFileSync(join(output, "typert.remote-client.d.ts.map"), artifact.remote.dtsMap);
4939
+ }
4940
+ }
4941
+ if (!emittedRemote && artifacts.some((artifact) => artifact.face === "host")) for (const file of REMOTE_CLIENT_FILES) rmSync(join(output, file), { force: true });
4942
+ }
4943
+ /**
4944
+ * Write every artifact of one workspace pass into its own package, in
4945
+ * artifact order, with {@link writePackageArtifacts}. A failed write stops
4946
+ * the pass; earlier packages keep their new files and later ones keep their
4947
+ * previous files.
4948
+ * @param root - workspace root that each artifact's `packageRoot` is relative to.
4949
+ * @param artifacts - artifacts returned by one `WorkspaceTypertGenerator` pass.
4950
+ * @throws A file-system error when an output directory or file cannot be written.
4951
+ */
4952
+ function writeWorkspaceArtifacts(root, artifacts) {
4953
+ for (const artifact of artifacts) writePackageArtifacts(join(root, artifact.packageRoot), [artifact]);
4954
+ }
4955
+ //#endregion
4956
+ export { CordisCatalogProjector, FaceModelEmitter, REGION_BEGIN, REGION_END, TypeGraphRenderError, TypeGraphRenderer, TypertAnalysisError, TypertEmitError, WorkspaceAnalyzer, WorkspaceCaches, WorkspaceTypertGenerator, collectEvents, collectServices, findWorkspaceRoot, projectCordisCatalog, renderInheritedPage, renderPageRegion, writePackageArtifacts, writeWorkspaceArtifacts };