gen-cordis-catalog.ts 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. /**
  2. * Generate the per-subsystem Cordis service/event reference regions from the
  3. * Typert catalog projection. Every harness `ctx.<key>` service and event scope
  4. * maps to exactly one `docs/subsystems/` page through the curated tables below;
  5. * the generator injects each page's Cordis API reference between its GENERATED markers —
  6. * into both language sides of the pair, localizing paired document paths for
  7. * the Chinese side while retaining every other byte — and re-records a pair's
  8. * `.i18n.yaml` only when nothing outside the region changed. The
  9. * projection enforces event modes, JSDoc parameter/return completeness, and
  10. * signature type-link coverage; the inherited (vendor) tier renders to
  11. * `docs/cordis-api/inherited.md`. `--check` verifies every generated artifact.
  12. *
  13. * Generated regions embed `file:line` source pointers, so inserting lines ABOVE a
  14. * recorded symbol makes the committed output stale even though nothing about the
  15. * symbol changed. Regenerate after editing any file this projection records — the
  16. * failure otherwise surfaces as the "reproduces every committed catalog artifact
  17. * byte for byte" test failing, which reads like a snapshot regression rather than
  18. * a missing regeneration.
  19. */
  20. import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
  21. import { dirname, resolve } from 'node:path'
  22. import {
  23. projectCordisCatalog,
  24. renderInheritedPage,
  25. renderPageRegion,
  26. REGION_BEGIN,
  27. REGION_END,
  28. } from '@deepseek-ai/dsh-typert-generator'
  29. import type { CordisCatalogPolicy } from '@deepseek-ai/dsh-typert-generator'
  30. import { renderCordisCoreApiPages } from './cordis-core-api.ts'
  31. import { contextKeyMap, contextMergeFiles, eventNameList } from './cordis-walk.ts'
  32. import {
  33. blobHash,
  34. parsePairMeta,
  35. parseTranslationPairingManifest,
  36. partitionGeneratedRegions,
  37. renderPairMeta,
  38. translationPairSourcePredicate,
  39. } from './translation-pairing.ts'
  40. import { rewriteTranslationLinkLocales } from './translation-links.ts'
  41. const root = resolve(import.meta.dirname, '..')
  42. const SUBSYSTEMS_DIR = 'docs/subsystems'
  43. const OUT_INHERITED = 'docs/cordis-api/inherited.md'
  44. const OUT_RUNTIME_API = 'packages/extensions/tool-cordis/src/api-catalog.ts'
  45. export { REGION_BEGIN, REGION_END }
  46. /**
  47. * The owning subsystems page for every harness `ctx.<key>` service the
  48. * projection discovers. Fail-closed both ways: a discovered key absent here
  49. * and an entry whose key the projection no longer discovers are both hard
  50. * errors, so the partition can never silently drift from the service API.
  51. */
  52. export const SERVICE_PAGE: Record<string, string> = {
  53. connection: 'web-server.md',
  54. pluginManager: 'boot.md',
  55. profileContext: 'boot.md',
  56. hmr: 'boot.md',
  57. mcpResources: 'mcp.md',
  58. agentLoop: 'core.md',
  59. agentDefaultModel: 'core.md',
  60. agentPresets: 'core.md',
  61. agents: 'core.md',
  62. approval: 'approval.md',
  63. officeToPdf: 'office-to-pdf.md',
  64. attachments: 'attachment.md',
  65. shell: 'shell.md',
  66. shellEnv: 'shell.md',
  67. clientModules: 'client-modules.md',
  68. ptcRuntime: 'ptc-runtime.md',
  69. browserUse: 'browser-use.md',
  70. computerUse: 'computer-use.md',
  71. commands: 'commands.md',
  72. compaction: 'compaction.md',
  73. cordisInspect: 'extensions.md',
  74. authorization: 'credentials.md',
  75. credentials: 'credentials.md',
  76. credentialsController: 'credentials.md',
  77. settingsController: 'settings.md',
  78. directoryPicker: 'workspace.md',
  79. deepseekLlmApiExtensions: 'llm-streaming.md',
  80. dynamicCordisRunner: 'extensions.md',
  81. fileUploads: 'attachment.md',
  82. fileReferences: 'session-reference.md',
  83. fs: 'filesystem.md',
  84. goals: 'goal.md',
  85. inspector: 'extensions.md',
  86. webServer: 'web-server.md',
  87. invariants: 'invariants.md',
  88. llm: 'llm-streaming.md',
  89. lsp: 'lsp.md',
  90. messageFeedback: 'feedback.md',
  91. sessionFeedback: 'feedback.md',
  92. permissionPresets: 'permission-presets.md',
  93. planMode: 'plan.md',
  94. terminals: 'terminal.md',
  95. sandbox: 'sandbox.md',
  96. sandboxPolicy: 'sandbox.md',
  97. ssh: 'ssh.md',
  98. sessionPersistence: 'persistence.md',
  99. sessionQuery: 'session-query.md',
  100. sessionFileReferences: 'session-reference.md',
  101. sessionReferenceResolver: 'session-reference.md',
  102. sessionProjectionCache: 'session-projection.md',
  103. sessionProjections: 'session-projection.md',
  104. sessionController: 'session.md',
  105. sessionSkillCatalog: 'skills.md',
  106. sessions: 'session.md',
  107. settings: 'settings.md',
  108. sessionTitle: 'session-title.md',
  109. skills: 'skills.md',
  110. spillStore: 'spill.md',
  111. storage: 'storage.md',
  112. storageDomain: 'storage.md',
  113. subagentModelSelection: 'subagent.md',
  114. subagents: 'subagent.md',
  115. subprocess: 'subprocess.md',
  116. systemPrompt: 'system-prompt.md',
  117. jobs: 'jobs.md',
  118. sessionTelemetry: 'session-telemetry.md',
  119. agentTeams: 'agent-team.md',
  120. tokenMeter: 'token-meter.md',
  121. toolResultPruner: 'compaction.md',
  122. tools: 'tools.md',
  123. typert: 'typert.md',
  124. typertGateway: 'typert.md',
  125. userQuestions: 'user-questions.md',
  126. web: 'web.md',
  127. workflowEngine: 'workflow.md',
  128. webhookRuntime: 'webhook.md',
  129. workspaceRegistry: 'workspace.md',
  130. workspaceController: 'workspace.md',
  131. workspaceFiles: 'workspace.md',
  132. workspaceChanges: 'deliverables.md',
  133. terminalController: 'workspace.md',
  134. directoryPickerController: 'workspace.md',
  135. }
  136. /**
  137. * Context keys declared in `interface Context` merges that the rendering
  138. * projection cannot see, each with the reason and its documentation owner.
  139. * The scan that enforces this list reads EVERY `declare module '@deepseek-ai/cordis'`
  140. * Context merge under `packages/x/x/src/**` — any depth, not only root
  141. * `index.ts` files with a same-named service class — so a new service can
  142. * never silently join this blind spot: it either enters {@link SERVICE_PAGE}
  143. * or names itself here. Client-face keys (the projection analyzes the host
  144. * face only) name the package README that owns their surface.
  145. *
  146. * Two categories remain, and neither is a projection gap a scanning rule could
  147. * close. An OPTIONAL key (`key?: X`) is a value the launcher or boot code
  148. * installs before the tree mounts, which the analyzer skips by rule because no
  149. * plugin provides it and `inject` cannot reach it. A client-face key belongs to
  150. * the browser Context, which this host-face program never sees; the browser
  151. * surface has its own generated catalog (`scripts/gen-client-catalog.ts`, served
  152. * to a model as `cordis_runtime_inspect what:"client"`).
  153. */
  154. export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
  155. webTerminals: 'client-side terminal view models — packages/api/terminal-controller/README.md owns the API',
  156. appReady: 'not a service: launcher-provided successful-startup signal — packages/boot/cmdline/README.md owns the launcher contract',
  157. appExit: 'not a service: launcher-provided bounded process-exit callback — packages/boot/cmdline/README.md owns the launcher contract',
  158. cmdlineArgs: 'not a service: launcher-provided immutable app argument accessor — packages/boot/cmdline/README.md owns the launcher contract',
  159. configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns this launcher contract',
  160. launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns this launcher contract',
  161. dshHomePath: 'not a service: boot-provided root accessor function (typeof dshHomePath | undefined) for Loader !!js config expressions — packages/boot/app-boot/README.md owns the boot contract',
  162. launchEnvironment: 'not a service: launcher-provided root accessor value (LaunchEnvironmentSnapshot | undefined) — packages/util/launch-environment/README.md owns this launcher contract',
  163. pluginPackages: 'profile-boot-owned package resolver service used by optional consumers — packages/boot/app-boot/README.md owns this internal API',
  164. fileUpload: 'client-side browser upload service — packages/client/file-upload/README.md owns the API',
  165. uiRenderer: 'client-side interface-typed browser service — packages/client/ui-renderer/README.md owns the API',
  166. uiSession: 'client-side Session source adapter — packages/client/ui-session/README.md owns the API',
  167. uiConversation: 'client-side Conversation registries and assembler — packages/client/ui-conversation/README.md owns the API',
  168. uiWorkspace: 'client-side Workspace navigation adapter — packages/client/ui-workspace/README.md owns the API',
  169. settingsSchema: 'client-side schema introspection service — packages/client/ui-settings/README.md owns the API',
  170. settingsScope: 'client-side settings-namespace transport service — packages/client/ui-settings/README.md owns the API',
  171. chatFileMentions: 'client-side slot-contract accessor (ChatFileMentions) — packages/client/ui-chat/README.md owns the API',
  172. commandUi: 'client-side interface-typed browser service — packages/client/ui-commands/README.md owns the API',
  173. conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the API',
  174. layout: 'client-side interface-typed browser service — packages/client/ui-layout/README.md owns the API',
  175. locale: 'client-side interface-typed browser service — packages/client/locale/README.md owns the API',
  176. modelDirectories: 'client-side interface-typed browser service — packages/client/ui-model-selection/README.md owns the API',
  177. modules: 'client-side interface-typed browser service — packages/client/modules/README.md owns the API',
  178. remote: 'client-side interface-typed gateway accessor (ClientRemote) — packages/api/gateway/README.md owns the API',
  179. sessionLogDownload: 'client-side browser download controller — packages/session-query/session-log-export/README.md owns the API',
  180. inputTriggers: 'client-side interface-typed browser service — packages/client/ui-input-trigger/README.md owns the API',
  181. timer: 'client-side dynamic-package timer service — packages/extensions/cordis-client-runner/README.md owns the API',
  182. slots: 'client-side interface-typed browser service — packages/client/ui-renderer/README.md owns the API',
  183. theme: 'client-side interface-typed browser service — packages/client/ui-theme/README.md owns the API',
  184. workspaces: 'client-side interface-typed browser service — packages/api/workspace-controller/README.md owns the API',
  185. resources: 'client-side resource model (protocol providers, pins, live sources) — packages/client/resources/README.md owns the API',
  186. sidebarRight: 'client-side right-Sidebar navigation face — packages/client/ui-sidebar-right/README.md owns the API',
  187. sidebarRightTabs: 'client-side right-Sidebar tab-type registry — packages/client/ui-sidebar-right/README.md owns the API',
  188. documentPreviews: 'client-side document renderer registry — docs/subsystems/sidebar-right.md owns the API',
  189. }
  190. /**
  191. * The owning subsystems page for every harness event scope (the segment
  192. * before the first `/`) the projection renders. Fail-closed exactly like
  193. * {@link SERVICE_PAGE}. Client-face events (`slash/*`, `theme/change`, …) are
  194. * invisible to the host-face projection and therefore never reach this map;
  195. * {@link EVENT_WALK_EXEMPTIONS} names each one with its documentation owner.
  196. */
  197. export const EVENT_SCOPE_PAGE: Record<string, string> = {
  198. hmr: 'boot.md',
  199. 'plugin-manager': 'boot.md',
  200. 'agent': 'core.md',
  201. 'agent-loop': 'core.md',
  202. 'agent-preset': 'core.md',
  203. 'api-session': 'session.md',
  204. 'approval': 'approval.md',
  205. 'commands': 'commands.md',
  206. 'connection': 'web-server.md',
  207. 'compaction': 'compaction.md',
  208. 'cordis': 'extensions.md',
  209. 'authorization': 'credentials.md',
  210. 'credentials': 'credentials.md',
  211. 'domain': 'storage.md',
  212. 'fs': 'filesystem.md',
  213. 'goal': 'goal.md',
  214. 'llm': 'llm-streaming.md',
  215. 'permission-presets': 'permission-presets.md',
  216. 'session': 'session.md',
  217. 'settings': 'settings.md',
  218. 'skills': 'skills.md',
  219. 'subagent': 'subagent.md',
  220. 'system-prompt': 'system-prompt.md',
  221. 'session-telemetry': 'session-telemetry.md',
  222. 'feedback': 'feedback.md',
  223. 'tools': 'tools.md',
  224. 'user-questions': 'user-questions.md',
  225. 'webserver': 'web-server.md',
  226. 'workflow': 'workflow.md',
  227. }
  228. /**
  229. * Event names declared in `interface Events` merges that the rendering
  230. * projection cannot see, each with the reason and its documentation owner.
  231. * The mirror of {@link SERVICE_WALK_EXEMPTIONS} for events: an independent
  232. * scan reads EVERY `declare module '@deepseek-ai/cordis'` Events merge under
  233. * `packages/x/x/src/**`, so a declared event either renders onto a subsystems
  234. * page (via {@link EVENT_SCOPE_PAGE}) or names itself here — never vanishes
  235. * silently. Keys are full event names rather than scopes, so a scope-level
  236. * exemption cannot mask another declaration in that scope.
  237. */
  238. export const EVENT_WALK_EXEMPTIONS: Record<string, string> = {
  239. 'command/executed': 'client-face local command acknowledgment — packages/client/ui-commands/README.md owns the API',
  240. 'connection/reset': 'client-face transport signal — packages/api/session-controller/README.md owns the API',
  241. 'locale/change': 'client-face locale switch signal — packages/client/locale/README.md owns the API',
  242. 'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
  243. 'slash/input-consume-token': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
  244. 'slash/input-insert-reference': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
  245. 'slash/input-insert-text': 'client-face slash-input protocol — packages/client/ui-input-trigger/README.md owns the API',
  246. 'slots/changed': 'client-face slot invalidation signal — packages/client/ui-renderer/README.md owns the API',
  247. 'theme/change': 'client-face theme switch signal — packages/client/ui-theme/README.md owns the API',
  248. }
  249. /**
  250. * One primary subsystems page per project type used by a generated
  251. * signature. This stays curated because union names intentionally do not
  252. * reuse the type-equivalence manifest's map-symbol entries and some symbols
  253. * appear on more than one page.
  254. */
  255. export const LINK_MAP: Readonly<Record<string, string>> = {
  256. WorkspaceChangesSummary: 'deliverables.md',
  257. WorkspaceFileDiff: 'deliverables.md',
  258. Reload: 'boot.md',
  259. PluginInfo: 'boot.md',
  260. BundleInfo: 'boot.md',
  261. ChangeResult: 'boot.md',
  262. InstallBundleOptions: 'boot.md',
  263. PluginEntryId: 'boot.md',
  264. BundleRowInfo: 'boot.md',
  265. PluginInstallCancellation: 'boot.md',
  266. PluginInstallRequestId: 'boot.md',
  267. PluginSpecInspection: 'boot.md',
  268. PluginChange: 'boot.md',
  269. PluginInstallLogChunk: 'boot.md',
  270. PluginInstallProgress: 'boot.md',
  271. BrowserUseProviderName: 'browser-use.md',
  272. ComputerUseProviderName: 'computer-use.md',
  273. RenderedDocumentBytes: 'office-to-pdf.md',
  274. OfficeToPdfRequest: 'office-to-pdf.md',
  275. OfficeToPdfResult: 'office-to-pdf.md',
  276. OfficeToPdfPriority: 'office-to-pdf.md',
  277. OfficeToPdfGeneration: 'office-to-pdf.md',
  278. Agent: 'core.md',
  279. AgentCancelCause: 'core.md',
  280. AgentFactory: 'core.md',
  281. AgentHandle: 'core.md',
  282. ModelSelection: 'core.md',
  283. AllowedModelRoute: 'subagent.md',
  284. SubagentModelSelectionSettings: 'subagent.md',
  285. AgentOptions: 'core.md',
  286. AgentStatus: 'core.md',
  287. AssistantStreamFrame: 'core.md',
  288. ContentBlock: 'llm-streaming.md',
  289. CreateAgentOptions: 'core.md',
  290. GenerateOptions: 'llm-streaming.md',
  291. Inbox: 'core.md',
  292. InboxItem: 'core.md',
  293. InboxPlacement: 'core.md',
  294. InspectorJsonValue: 'extensions.md',
  295. MessageId: 'llm-streaming.md',
  296. ResumeAgentOptions: 'core.md',
  297. SettleReason: 'core.md',
  298. AdapterRegistrationHandle: 'llm-streaming.md',
  299. DirectoryRegistrationHandle: 'llm-streaming.md',
  300. DeepSeekLlmApiExtensionMap: 'llm-streaming.md',
  301. DeepSeekLlmApiExtensionProvider: 'llm-streaming.md',
  302. DeepSeekLlmApiExtensionRequest: 'llm-streaming.md',
  303. LlmCallConfig: 'llm-streaming.md',
  304. LlmModelContext: 'llm-streaming.md',
  305. LlmModelReasoningInfo: 'llm-streaming.md',
  306. LlmResolvedModelInfo: 'llm-streaming.md',
  307. LlmFailure: 'llm-streaming.md',
  308. LlmImageRequestPricing: 'llm-streaming.md',
  309. LlmModelInfo: 'llm-streaming.md',
  310. LlmProviderInfo: 'llm-streaming.md',
  311. LlmConfigurableProvider: 'llm-streaming.md',
  312. LlmModelDiscoveryRequest: 'llm-streaming.md',
  313. LlmDiscoveredModel: 'llm-streaming.md',
  314. ResolvedRetryPolicy: 'llm-streaming.md',
  315. Message: 'llm-streaming.md',
  316. MessageSource: 'llm-streaming.md',
  317. MessageFeedbackDeleteRequest: 'feedback.md',
  318. MessageFeedbackDeleteResult: 'feedback.md',
  319. MessageFeedbackDeleteValue: 'feedback.md',
  320. MessageFeedbackFailure: 'feedback.md',
  321. MessageFeedbackItem: 'feedback.md',
  322. MessageFeedbackListRequest: 'feedback.md',
  323. SessionFeedbackRecordRequest: 'feedback.md',
  324. SessionFeedbackRecordResult: 'feedback.md',
  325. MessageFeedbackListResult: 'feedback.md',
  326. MessageFeedbackListValue: 'feedback.md',
  327. MessageFeedbackNoteBlank: 'feedback.md',
  328. MessageFeedbackNoteTooLarge: 'feedback.md',
  329. MessageFeedbackPutRequest: 'feedback.md',
  330. MessageFeedbackPutResult: 'feedback.md',
  331. MessageFeedbackRating: 'feedback.md',
  332. MessageFeedbackRejected: 'feedback.md',
  333. MessageFeedbackSessionNotFound: 'feedback.md',
  334. MessageFeedbackSuccess: 'feedback.md',
  335. MessageFeedbackTargetNotFound: 'feedback.md',
  336. MessageFeedbackVersion: 'feedback.md',
  337. MessageFeedbackVersionConflict: 'feedback.md',
  338. UserMessage: 'session.md',
  339. ApiSessionAgentResult: 'session.md',
  340. PreStepDecision: 'core.md',
  341. PreStepContext: 'core.md',
  342. RequestErrorAction: 'core.md',
  343. RequestFailureContext: 'core.md',
  344. PreparedReferencedMessage: 'session-reference.md',
  345. FileReferenceCandidate: 'session-reference.md',
  346. SessionReferenceCandidate: 'session-reference.md',
  347. SessionReferenceMentionCandidate: 'session-reference.md',
  348. SessionReferenceInput: 'session-reference.md',
  349. SessionAttachmentRequest: 'session.md',
  350. SessionAttachmentValue: 'session.md',
  351. SessionCancelRequest: 'session.md',
  352. SessionCancelValue: 'session.md',
  353. SessionControlFrame: 'session.md',
  354. SessionCreateRequest: 'session.md',
  355. SessionCreateValue: 'session.md',
  356. SessionEvent: 'session.md',
  357. SessionFollowFrame: 'session.md',
  358. SessionFollowRequest: 'session.md',
  359. SessionForkRequest: 'session.md',
  360. SessionForkValue: 'session.md',
  361. SessionId: 'core.md',
  362. SessionLogOffset: 'session.md',
  363. SessionSeq: 'session.md',
  364. SessionSeqCursor: 'session.md',
  365. OptionalSessionSeq: 'session.md',
  366. SessionListRequest: 'session.md',
  367. SessionListValue: 'session.md',
  368. ModelCatalog: 'session.md',
  369. SessionOpenWorkspacePathRequest: 'session.md',
  370. SessionOpenWorkspacePathValue: 'session.md',
  371. SessionModels: 'session.md',
  372. SessionModelsRequest: 'session.md',
  373. SessionPage: 'session.md',
  374. SessionPageRequest: 'session.md',
  375. SessionPromptRequest: 'session.md',
  376. SessionPromptValue: 'session.md',
  377. SessionRenameRequest: 'session.md',
  378. SessionRenameValue: 'session.md',
  379. SessionRespondReceipt: 'session.md',
  380. SessionRespondRequest: 'session.md',
  381. SessionSearchValue: 'session.md',
  382. SessionSelectModelRequest: 'session.md',
  383. SessionSelectModelValue: 'session.md',
  384. SessionSummary: 'session.md',
  385. SessionMessageProjection: 'session.md',
  386. SessionUpdateQueueRequest: 'session.md',
  387. SessionUpdateQueueValue: 'session.md',
  388. EncodedFileUploadRequest: 'attachment.md',
  389. AgentResolver: 'attachment.md',
  390. PromptFileBinding: 'attachment.md',
  391. FileUploadReceiptId: 'attachment.md',
  392. FileUploadValue: 'attachment.md',
  393. SessionStartSource: 'core.md',
  394. SessionLogSnapshot: 'session-query.md',
  395. SessionSurfaceSnapshot: 'session-query.md',
  396. ApprovalOutcome: 'approval.md',
  397. ApprovalPolicy: 'approval.md',
  398. ApprovalRequest: 'approval.md',
  399. ApprovalRequestEvent: 'approval.md',
  400. ApprovalService: 'approval.md',
  401. AskUserQuestionRequestEvent: 'user-questions.md',
  402. AdmittedPromptContentPart: 'attachment.md',
  403. AttachmentAdmissionPart: 'attachment.md',
  404. AttachmentError: 'attachment.md',
  405. EncodedFileAttachment: 'attachment.md',
  406. EncodedImageAttachment: 'attachment.md',
  407. FileAttachmentRef: 'attachment.md',
  408. SaveFileAttachment: 'attachment.md',
  409. SaveFileStreamAttachment: 'attachment.md',
  410. ImageAttachmentAccess: 'llm-streaming.md',
  411. ImageAttachmentRef: 'attachment.md',
  412. ImageRequestTarget: 'attachment.md',
  413. ProjectedDimensions: 'attachment.md',
  414. PromptContentPart: 'attachment.md',
  415. RequestImageAttachment: 'attachment.md',
  416. SaveImageAttachment: 'attachment.md',
  417. StoredImageAttachment: 'attachment.md',
  418. ShellExecRequest: 'shell.md',
  419. ShellExecSpec: 'shell.md',
  420. ShellProcess: 'shell.md',
  421. ShellRunResult: 'shell.md',
  422. DshEnvironment: 'subprocess.md',
  423. SubprocessHandle: 'subprocess.md',
  424. SubprocessOutcome: 'subprocess.md',
  425. SubprocessOutputRead: 'subprocess.md',
  426. SubprocessOutputReader: 'subprocess.md',
  427. SubprocessSpawnSpec: 'subprocess.md',
  428. SubprocessTerminalHandle: 'subprocess.md',
  429. SubprocessTerminalEnvironment: 'subprocess.md',
  430. SubprocessTerminalSpawnSpec: 'subprocess.md',
  431. PtcRunRequest: 'ptc-runtime.md',
  432. PtcRunSpec: 'ptc-runtime.md',
  433. PtcRunSandbox: 'ptc-runtime.md',
  434. PtcRunResult: 'ptc-runtime.md',
  435. CompactionResult: 'compaction.md',
  436. CompactionTrigger: 'compaction.md',
  437. PruneResult: 'compaction.md',
  438. FileReadOutcome: 'filesystem.md',
  439. FsDirEntry: 'filesystem.md',
  440. FsEditOutcome: 'filesystem.md',
  441. FsEditRequest: 'filesystem.md',
  442. FsInfo: 'filesystem.md',
  443. FsObservation: 'filesystem.md',
  444. FsPathInfo: 'filesystem.md',
  445. FsObservationActor: 'filesystem.md',
  446. FsTarget: 'filesystem.md',
  447. FsVersion: 'filesystem.md',
  448. FsWriteIntent: 'filesystem.md',
  449. FsWriteOutcome: 'filesystem.md',
  450. CreateGoalRequest: 'goal.md',
  451. EditGoalRequest: 'goal.md',
  452. GoalBlockReason: 'goal.md',
  453. GoalActivationChanged: 'goal.md',
  454. GoalChanged: 'goal.md',
  455. GoalRef: 'goal.md',
  456. GoalView: 'goal.md',
  457. CreateGoalResult: 'goal.md',
  458. CommandDefinition: 'commands.md',
  459. CommandDescriptor: 'commands.md',
  460. CommandFileReceiptResolver: 'commands.md',
  461. CommandId: 'commands.md',
  462. CommandResult: 'commands.md',
  463. CommandSubmitAttachment: 'commands.md',
  464. CommandSurface: 'commands.md',
  465. LspProvider: 'lsp.md',
  466. LspQueryRequest: 'lsp.md',
  467. LspQueryResult: 'lsp.md',
  468. LlmAdapter: 'llm-streaming.md',
  469. PreparedLlmCall: 'llm-streaming.md',
  470. PreparedDeepSeekLlmApiExtensions: 'llm-streaming.md',
  471. LlmRuntime: 'llm-streaming.md',
  472. StreamChunk: 'llm-streaming.md',
  473. SkillProviderControl: 'skills.md',
  474. CreateSessionOptions: 'persistence.md',
  475. PrepareSessionOptions: 'persistence.md',
  476. SessionHeader: 'persistence.md',
  477. SessionLocation: 'persistence.md',
  478. SessionPreparation: 'persistence.md',
  479. SessionAccess: 'persistence.md',
  480. SessionHandle: 'persistence.md',
  481. SessionPersistenceCreateOptions: 'persistence.md',
  482. SessionPersistenceOpenOptions: 'persistence.md',
  483. SessionPersistenceStatOptions: 'persistence.md',
  484. SessionPersistenceListOptions: 'persistence.md',
  485. SessionPersistenceSnapshot: 'persistence.md',
  486. SessionInspection: 'persistence.md',
  487. SessionStorageMetadata: 'persistence.md',
  488. ConfinedArgv: 'sandbox.md',
  489. SandboxExecutionPolicy: 'sandbox.md',
  490. SandboxMode: 'sandbox.md',
  491. SandboxPolicy: 'sandbox.md',
  492. TerminalBackend: 'terminal.md',
  493. TerminalReadRequest: 'terminal.md',
  494. TerminalReadResult: 'terminal.md',
  495. TerminalSendOperation: 'terminal.md',
  496. TerminalSendRequest: 'terminal.md',
  497. TerminalSessionId: 'terminal.md',
  498. TerminalSessionSnapshot: 'terminal.md',
  499. TerminalSignal: 'terminal.md',
  500. TerminalSignalResult: 'terminal.md',
  501. TerminalSpawnRequest: 'terminal.md',
  502. TerminalSpawnResult: 'terminal.md',
  503. SandboxPolicyRequest: 'sandbox.md',
  504. SshConnection: 'ssh.md',
  505. SshStreamEndpoint: 'ssh.md',
  506. ScopeKey: 'scope.md',
  507. Scoped: 'scope.md',
  508. EpochHeader: 'session.md',
  509. Session: 'session.md',
  510. SessionEventMap: 'session.md',
  511. TurnEndReason: 'session.md',
  512. TurnTrigger: 'session.md',
  513. SessionEventReadRequest: 'session-query.md',
  514. SessionEventRecord: 'session-query.md',
  515. SessionEventResultFilter: 'session-query.md',
  516. SessionEventSearchDocument: 'session-query.md',
  517. SessionEventSearchHit: 'session-query.md',
  518. SessionEventSearchPage: 'session-query.md',
  519. SessionEventSearchRequest: 'session-query.md',
  520. SessionEventTrace: 'session-query.md',
  521. SessionEventTraceObservation: 'session-query.md',
  522. SessionEventTraceRequest: 'session-query.md',
  523. SessionEventWindow: 'session-query.md',
  524. SessionLineageTrace: 'session-query.md',
  525. SessionObservation: 'session-query.md',
  526. SessionObservationOptions: 'session-query.md',
  527. SessionRecord: 'session-query.md',
  528. SessionResultFilter: 'session-query.md',
  529. SessionSearchExecContext: 'session-query.md',
  530. SessionSearchHit: 'session-query.md',
  531. SessionSearchPage: 'session-query.md',
  532. SessionSearchRequest: 'session-query.md',
  533. SessionTitleObservation: 'session-query.md',
  534. SessionTitleObservationResult: 'session-query.md',
  535. SessionTitleProvider: 'session-title.md',
  536. SessionTitleSnapshot: 'session-title.md',
  537. SkillCatalogSnapshot: 'skills.md',
  538. SkillDefinition: 'skills.md',
  539. SkillLookupOptions: 'skills.md',
  540. SkillProvider: 'skills.md',
  541. SkillProviderObservation: 'skills.md',
  542. SkillRegistration: 'skills.md',
  543. SkillViewOptions: 'skills.md',
  544. SkillSummary: 'skills.md',
  545. SaveTextSpill: 'spill.md',
  546. SpillRef: 'spill.md',
  547. ContinuableCreateRequest: 'subagent.md',
  548. ContinuableCreateSpec: 'subagent.md',
  549. ContinuableStart: 'subagent.md',
  550. ContinuableStartSpec: 'subagent.md',
  551. AgentMessageSource: 'subagent.md',
  552. SubagentCatalog: 'subagent.md',
  553. SubagentDescendantListEntry: 'subagent.md',
  554. SubagentSendMessageOptions: 'subagent.md',
  555. SubagentInterruptAuthority: 'subagent.md',
  556. SubagentInterruptReceipt: 'subagent.md',
  557. SubagentListEntry: 'subagent.md',
  558. SubagentPromptReceipt: 'subagent.md',
  559. SubagentPromptRequest: 'subagent.md',
  560. SubagentProvider: 'subagent.md',
  561. SubagentRun: 'subagent.md',
  562. SubagentRuntime: 'subagent.md',
  563. SubagentStartRequest: 'subagent.md',
  564. AssembleContext: 'system-prompt.md',
  565. PromptContext: 'system-prompt.md',
  566. PromptContextOrderName: 'system-prompt.md',
  567. PromptSection: 'system-prompt.md',
  568. PromptSectionOrderName: 'system-prompt.md',
  569. SystemPrompt: 'system-prompt.md',
  570. ToolProviderResult: 'system-prompt.md',
  571. JobDoneListener: 'jobs.md',
  572. JobId: 'jobs.md',
  573. JobRead: 'jobs.md',
  574. JobSnapshot: 'jobs.md',
  575. JobStart: 'jobs.md',
  576. JobsChangedListener: 'jobs.md',
  577. CreateTeamTaskRequest: 'agent-team.md',
  578. SendTeamMessageRequest: 'agent-team.md',
  579. SendTeamMessageResult: 'agent-team.md',
  580. SpawnTeammateRequest: 'agent-team.md',
  581. SpawnTeammateResult: 'agent-team.md',
  582. TeamId: 'agent-team.md',
  583. TeamMemberView: 'agent-team.md',
  584. TeamMembership: 'agent-team.md',
  585. TeamTaskMutationResult: 'agent-team.md',
  586. TeamTaskId: 'agent-team.md',
  587. TeamTaskView: 'agent-team.md',
  588. TeamView: 'agent-team.md',
  589. TeamWaitResult: 'agent-team.md',
  590. UpdateTeamTaskRequest: 'agent-team.md',
  591. TokenMeasurement: 'token-meter.md',
  592. PtcDispatchLog: 'tools.md',
  593. PostToolDecision: 'tools.md',
  594. PreToolDecision: 'tools.md',
  595. ToolDefinition: 'tools.md',
  596. ToolExecution: 'tools.md',
  597. ToolDispatchExecution: 'tools.md',
  598. ToolExecutionInput: 'tools.md',
  599. ToolExecutionMode: 'tools.md',
  600. ToolExecutionResult: 'tools.md',
  601. ToolExecutionToken: 'tools.md',
  602. ToolGuard: 'tools.md',
  603. ToolPresentationMode: 'tools.md',
  604. ToolRuntime: 'tools.md',
  605. ToolRestriction: 'tools.md',
  606. ToolSchema: 'tools.md',
  607. SettingsNamespace: 'settings.md',
  608. SettingsNamespaceInput: 'settings.md',
  609. SettingsRegisterOptions: 'settings.md',
  610. SettingsSectionHooks: 'settings.md',
  611. SettingsScope: 'settings.md',
  612. SettingsDescriptor: 'settings.md',
  613. SettingsDescribeValue: 'settings.md',
  614. SettingsDocumentOpenValue: 'settings.md',
  615. AgentPresetDirectoryOpenValue: 'settings.md',
  616. SettingsNamespaceView: 'settings.md',
  617. SettingsPathOpView: 'settings.md',
  618. SettingsSecretView: 'settings.md',
  619. SettingsPathOp: 'settings.md',
  620. SettingsDescribeOptions: 'settings.md',
  621. SettingsUpdateSource: 'settings.md',
  622. SkillListRequest: 'skills.md',
  623. SkillListValue: 'skills.md',
  624. AuthorizationEntry: 'credentials.md',
  625. AuthorizationFlow: 'credentials.md',
  626. AuthorizationInteraction: 'credentials.md',
  627. AuthorizationMethod: 'credentials.md',
  628. AuthorizationNotice: 'credentials.md',
  629. AuthorizationOutcome: 'credentials.md',
  630. AuthorizationPrompt: 'credentials.md',
  631. AuthorizationRequest: 'credentials.md',
  632. AuthorizationSession: 'credentials.md',
  633. AuthorizationSettlement: 'credentials.md',
  634. AuthorizationStatus: 'credentials.md',
  635. CredentialRef: 'credentials.md',
  636. CredentialKey: 'credentials.md',
  637. CredentialInfo: 'credentials.md',
  638. CredentialRecord: 'credentials.md',
  639. CredentialRecordEntry: 'credentials.md',
  640. CredentialRecordInfo: 'credentials.md',
  641. ResolvedCredential: 'credentials.md',
  642. AskUserQuestionAnswer: 'user-questions.md',
  643. AskUserQuestionRequest: 'user-questions.md',
  644. UserQuestionProvider: 'user-questions.md',
  645. WebFetchProvider: 'web.md',
  646. WebFetchRequest: 'web.md',
  647. WebFetchResult: 'web.md',
  648. WebSearchProvider: 'web.md',
  649. WebSearchRequest: 'web.md',
  650. WebSearchResult: 'web.md',
  651. WorkflowRun: 'workflow.md',
  652. VerifiedWebhookDelivery: 'webhook.md',
  653. WebhookRule: 'webhook.md',
  654. PermissionCatalog: 'permission-presets.md',
  655. PresetOption: 'permission-presets.md',
  656. PresetSpec: 'permission-presets.md',
  657. InvariantInstaller: 'invariants.md',
  658. WebRoute: 'web-server.md',
  659. IndexInjection: 'web-server.md',
  660. StorageBackend: 'storage.md',
  661. StorageForms: 'storage.md',
  662. Domain: 'storage.md',
  663. DomainSpec: 'storage.md',
  664. DomainChanged: 'storage.md',
  665. DomainFacility: 'storage.md',
  666. Workspace: 'workspace.md',
  667. WorkspaceArchiveSessionRequest: 'workspace.md',
  668. WorkspaceArchiveValue: 'workspace.md',
  669. WorkspaceCreateRequest: 'workspace.md',
  670. WorkspaceCreateValue: 'workspace.md',
  671. WorkspaceDeleteRequest: 'workspace.md',
  672. WorkspaceDeleteValue: 'workspace.md',
  673. WorkspaceFollowFrame: 'workspace.md',
  674. WorkspaceId: 'workspace.md',
  675. WorkspaceInsertBeforeRequest: 'workspace.md',
  676. WorkspaceInsertSessionBeforeRequest: 'workspace.md',
  677. WorkspaceOrderValue: 'workspace.md',
  678. WorkspaceRenameRequest: 'workspace.md',
  679. WorkspaceUnarchiveSessionRequest: 'workspace.md',
  680. WorkspaceValue: 'workspace.md',
  681. ClientArtifactBaseline: 'client-modules.md',
  682. WebBootGraph: 'client-modules.md',
  683. SessionTelemetryRecord: 'session-telemetry.md',
  684. WorkflowRunInfo: 'workflow.md',
  685. WorkflowStartRequest: 'workflow.md',
  686. ProjectionDefinition: 'session-projection.md',
  687. SessionProjectionMap: 'session-projection.md',
  688. SessionProjectionStateMap: 'session-projection.md',
  689. ProjectionChangeListener: 'session-projection.md',
  690. ProjectionSnapshot: 'session-projection.md',
  691. ProjectionCheckpoint: 'session-projection.md',
  692. DirectoryPickerCapability: 'workspace.md',
  693. DirectoryListing: 'workspace.md',
  694. TypertContribution: 'invariants.md',
  695. TypertRemoteEventSource: 'typert.md',
  696. RemoteEventHostInfo: 'typert.md',
  697. TypertFace: 'invariants.md',
  698. TypertPackageFilter: 'invariants.md',
  699. TypertPackageRecord: 'invariants.md',
  700. TypertSchemaFilter: 'invariants.md',
  701. TypertSchemaRecord: 'invariants.md',
  702. }
  703. /** TypeScript lib and pinned framework types with no repository-owned data page. */
  704. export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
  705. 'Plugin',
  706. 'AbortSignal',
  707. 'AsyncIterable',
  708. 'Context',
  709. 'Error',
  710. 'EntryTree',
  711. 'EntryOptions',
  712. 'Exclude',
  713. 'Extract',
  714. 'Map',
  715. 'NonNullable',
  716. 'Omit',
  717. 'Partial',
  718. 'Pick',
  719. 'Promise',
  720. 'Record',
  721. 'Readonly',
  722. 'ReadonlyMap',
  723. 'Request',
  724. 'Response',
  725. 'IncomingMessage',
  726. 'ServerResponse',
  727. 'ReturnType',
  728. 'Uint8Array',
  729. ])
  730. /** Project types deliberately documented outside the subsystems catalog. */
  731. export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
  732. ConnectionFetchHandler: 'shared Fetch dispatch is owned by packages/client/connection/src/rpc.ts',
  733. ConnectionRequestRejection: 'transport rejection status is owned by packages/client/connection/src/rpc.ts',
  734. ConnectionTrustRequest: 'transport authentication input is owned by packages/client/connection/src/rpc.ts',
  735. ConnectionIndexRequest: 'frontend authentication request is owned by packages/client/connection/src/rpc.ts',
  736. ConnectionIndexResponse: 'frontend authentication response is owned by packages/client/connection/src/rpc.ts',
  737. Profile: 'resolved profile layers are owned by packages/boot/app-boot/README.md',
  738. PatchOptions: 'Include patch entries are owned by vendor/include (vendored upstream)',
  739. McpResourceProvider: 'scoped resource provider is owned by packages/mcp/mcp-resources/README.md',
  740. 'z.ZodType': 'Zod response validation API is owned by https://zod.dev/packages/zod',
  741. Socket: 'Node.js byte stream API is owned by https://nodejs.org/api/net.html#class-netsocket',
  742. z: 'schemastery schema constructor is owned by vendor/schemastery (vendored upstream)',
  743. BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
  744. InsertReferenceRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
  745. ConsumeTokenRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
  746. InsertTextRequest: 'event-local request contract is owned by packages/client/ui-input-trigger/src/types.ts',
  747. AgentHandle: 'agent ownership handle is owned by packages/core/agent/README.md',
  748. AgentPreset: 'discovered preset record is owned by packages/preset/agent-presets/README.md',
  749. AgentPresetRoster: 'path-free preset roster is owned by packages/preset/agent-presets/README.md',
  750. AgentPresetDocument: 'preset composition view is owned by packages/preset/agent-presets/README.md',
  751. AgentPresetComposition: 'flattened composition rows are owned by packages/preset/agent-presets/README.md',
  752. PresetMetadata: 'preset display text is owned by packages/preset/agent-presets/README.md',
  753. BashEnvContributor: 'service-local extension type is owned by packages/shell/tool-bash/src/index.ts',
  754. BashEnvVariableInfo: 'service-local metadata type is owned by packages/shell/tool-bash/src/index.ts',
  755. CompactionAgentContext: 'compaction service input is owned by packages/compaction/compaction/src/index.ts',
  756. ManualCompactAgentContext: 'manual compaction service input is owned by packages/compaction/compaction/src/index.ts',
  757. ClientResponse: 'wire response message is owned by packages/client/connection/src/rpc.ts',
  758. ApprovalRequestId: 'dynamic Plugin approval identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
  759. CordisErrorDetails: 'Cordis runtime error payload is owned by packages/extensions/cordis-host-runner/src/types.ts',
  760. CordisInspectPlatform: 'Cordis inspect platform identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
  761. CordisInspectProviderManifest: 'Cordis inspect provider manifest is owned by packages/extensions/cordis-host-runner/src/types.ts',
  762. CordisInspectProviderView: 'Cordis inspect provider view is owned by packages/extensions/cordis-host-runner/src/types.ts',
  763. CordisInspectQueryRequest: 'Cordis inspect transport payload is owned by packages/extensions/cordis-host-runner/src/types.ts',
  764. CordisInspectQueryResolution: 'Cordis inspect query result is owned by packages/extensions/cordis-host-runner/src/types.ts',
  765. CordisInspectQueryResolved: 'Cordis inspect transport payload is owned by packages/extensions/cordis-host-runner/src/types.ts',
  766. CordisInspectRequestId: 'Cordis inspect request identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
  767. CordisInspectResolveAck: 'Cordis inspect resolution acknowledgement is owned by packages/extensions/cordis-host-runner/src/types.ts',
  768. CordisDynamicPackageId: 'dynamic Package identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
  769. CordisDynamicPluginId: 'dynamic Plugin identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
  770. CordisDynamicPluginRunId: 'dynamic Plugin run identity is owned by packages/extensions/cordis-host-runner/src/types.ts',
  771. CordisDynamicRunMode: 'dynamic Plugin activation mode is owned by packages/extensions/cordis-host-runner/src/types.ts',
  772. DynamicCordisClientSource: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  773. DynamicCordisDefineReceipt: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  774. DynamicCordisDefineRequest: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  775. DynamicCordisHostHalfResult: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  776. DynamicCordisInventoryRow: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  777. DynamicCordisInvokeResult: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  778. DynamicCordisPackageInspection: 'dynamic Package source inspection is owned by packages/extensions/cordis-host-runner/src/registry.ts',
  779. DynamicCordisPluginInspection: 'dynamic Plugin inspection is owned by packages/extensions/cordis-host-runner/src/registry.ts',
  780. DynamicCordisRequestResolved: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  781. DynamicCordisRetracted: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  782. DynamicCordisRunRequest: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  783. DynamicCordisPackage: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  784. DynamicCordisReference: 'dynamic Plugin reference is owned by packages/extensions/cordis-host-runner/src/registry.ts',
  785. DynamicCordisRenderFailure: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  786. DynamicCordisResolveAck: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  787. DynamicCordisRunResolution: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  788. DynamicCordisRunResponse: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  789. DynamicCordisSnapshotRow: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  790. DynamicCordisStopResponse: 'dynamic Plugin stop result is owned by packages/extensions/cordis-host-runner/src/types.ts',
  791. DynamicCordisUndefineReceipt: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  792. HostCordisInspectProviderRegistration: 'Host inspect provider registration is owned by packages/extensions/cordis-host-runner/src/inspect-registry.ts',
  793. DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md',
  794. CommandExecution: 'executor return contract is owned by packages/interaction/commands/src/index.ts',
  795. 'z.core.JSONSchema.BaseSchema': 'zod projection output is owned by the zod v4 API',
  796. 'z.core.ToJSONSchemaParams': 'zod projection parameters are owned by the zod v4 API',
  797. TypertDisposer: 'Typert lifecycle contract is owned by packages/typert/protocol/README.md',
  798. InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md',
  799. LocaleDict: 'service-local dictionary fields are owned by packages/client/i18n/src/index.ts',
  800. ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
  801. Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
  802. WebUpgradeRoute:
  803. 'upgrade route registration contract is owned by packages/host/webserver/src/index.ts',
  804. InvariantRegistration: 'service-local lifecycle handle is owned by packages/runtime-diagnostics/invariants/README.md',
  805. JsonValue: 'JSON value union is owned by packages/core/session/src/json.ts',
  806. KnobState: 'projection unit state fields are owned by packages/interaction/permission-presets/README.md',
  807. PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md',
  808. RequestRunId: 'dynamic-package payload contract is owned by packages/extensions/cordis-host-runner/src/types.ts',
  809. RpcReceipt: 'carrier-layer receipt is owned by packages/client/connection/src/rpc.ts',
  810. SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts',
  811. SubagentRunEndInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts',
  812. SubagentRunInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts',
  813. WorkflowAgentEndInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
  814. WorkflowAgentInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
  815. WorkflowResultInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
  816. WorkspaceFileScope: 'Host workspace file lookup contract is owned by packages/api/workspace-files/README.md',
  817. WorkspaceByteRange: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  818. WorkspaceDirectoryListing: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  819. WorkspaceFileBytes: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  820. WorkspaceFileChange: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  821. WorkspaceFileWatchFrame: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  822. WorkspaceFileRange: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  823. WorkspaceFileStat: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  824. WorkspaceFileText: 'Host workspace file endpoint contract is owned by packages/api/workspace-files/README.md',
  825. TerminalShell: 'Browser terminal shell profiles are owned by packages/api/terminal-controller/README.md',
  826. TerminalEnvironment: 'Browser terminal environment fields are owned by packages/api/terminal-controller/README.md',
  827. WebTerminalInfo: 'Browser terminal metadata is owned by packages/api/terminal-controller/README.md',
  828. TerminalCreateRequest: 'Browser terminal allocation fields are owned by packages/api/terminal-controller/README.md',
  829. TerminalAttachmentId: 'Browser terminal input ownership is owned by packages/api/terminal-controller/README.md',
  830. TerminalFrame: 'Browser terminal stream frames are owned by packages/api/terminal-controller/README.md',
  831. TerminalRetentionFrame: 'Browser terminal window holds are owned by packages/api/terminal-controller/README.md',
  832. WebTerminalId: 'Browser terminal identity is owned by packages/api/terminal-controller/README.md',
  833. }
  834. /** Repository data policy consumed by the Cordis catalog projector. */
  835. export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
  836. linkedTypePages: LINK_MAP,
  837. foundationTypeNames: FOUNDATION_TYPE_NAMES,
  838. typeLinkExemptions: TYPE_LINK_EXEMPTIONS,
  839. runtimeServiceExclusions: new Set(['cordisInspect', 'dynamicCordisRunner']),
  840. runtimeServices: [{
  841. key: 'timer',
  842. type: 'TimerService',
  843. abstract: false,
  844. doc: 'Disposable timer helpers mixed into Cordis contexts.',
  845. source: 'vendor/timer/src/index.ts:12',
  846. methods: [
  847. {
  848. signature: 'timeout(callback: () => void, delay: number): () => void',
  849. jsDoc: '/** Run a callback once and return its disposer. */',
  850. },
  851. {
  852. signature: 'timeout(delay: number): Promise<void>',
  853. jsDoc: '/** Resolve after a delay; disposal rejects the pending promise. */',
  854. },
  855. {
  856. signature: 'interval(callback: () => void, delay: number): () => void',
  857. jsDoc: '/** Run a callback repeatedly and return its disposer. */',
  858. },
  859. {
  860. signature: 'interval<R = any>(delay: number): AsyncIterableIterator<void, R, void>',
  861. jsDoc: '/** Return an async iterator of timer ticks. */',
  862. },
  863. {
  864. signature: 'throttle<F extends (...args: any[]) => void>(callback: F, delay: number, noTrailing?: boolean): F & { dispose: () => void }',
  865. jsDoc: '/** Return a throttled function whose timer is disposed with the current fiber. */',
  866. },
  867. {
  868. signature: 'debounce<F extends (...args: any[]) => void>(callback: F, delay: number): F & { dispose: () => void }',
  869. jsDoc: '/** Return a debounced function whose timer is disposed with the current fiber. */',
  870. },
  871. ],
  872. }],
  873. inheritedEvents: [
  874. { name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:328' },
  875. { name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:330' },
  876. { name: 'internal/service', summary: 'Interception hook for a service binding (no core producer).', source: 'vendor/cordis/src/events.ts:332' },
  877. { name: 'internal/update', summary: 'Waterfall: a fiber config update is being applied.', source: 'vendor/cordis/src/events.ts:334' },
  878. { name: 'internal/get', summary: 'Waterfall: a service is being read from the store.', source: 'vendor/cordis/src/events.ts:336' },
  879. { name: 'internal/set', summary: 'Waterfall: a service is being written to the store.', source: 'vendor/cordis/src/events.ts:338' },
  880. { name: 'internal/listener', summary: 'A listener was registered.', source: 'vendor/cordis/src/events.ts:340' },
  881. { name: 'internal/dispatch', summary: 'An event is being dispatched to listeners.', source: 'vendor/cordis/src/events.ts:342' },
  882. { name: 'exit', summary: 'The process is exiting on a signal.', source: 'vendor/loader/src/index.ts:23' },
  883. { name: 'loader/config-update', summary: 'The loader config tree changed.', source: 'vendor/loader/src/index.ts:24' },
  884. { name: 'loader/entry-init', summary: 'A config entry is being initialized.', source: 'vendor/loader/src/index.ts:25' },
  885. { name: 'loader/partial-dispose', summary: 'An entry is being partially disposed on reload.', source: 'vendor/loader/src/index.ts:26' },
  886. { name: 'loader/patch-context', summary: 'A context is being patched during a reload.', source: 'vendor/loader/src/index.ts:27' },
  887. ],
  888. inheritedServices: [
  889. { name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).', source: 'vendor/cordis/src/events.ts:34' },
  890. { name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / short-circuit chain).', source: 'vendor/cordis/src/events.ts:34' },
  891. { name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.', source: 'vendor/cordis/src/registry.ts:164' },
  892. { name: 'ctx.effect', summary: 'Register a disposable side effect tied to the fiber.', source: 'vendor/cordis/src/fiber.ts:9' },
  893. { name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.', source: 'vendor/cordis/src/reflect.ts:7' },
  894. { name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:42' },
  895. { name: 'ctx.root / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger', summary: 'Ambient handles onto the running context graph.', source: 'vendor/cordis/src/context.ts:16' },
  896. { name: 'ctx.timer (+ interval / timeout / throttle / debounce)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the four supported helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
  897. { name: 'ctx.loader', summary: 'The config Loader that booted the app (present under the loader).', source: 'vendor/loader/src/index.ts:30' },
  898. ],
  899. }
  900. /**
  901. * Splice a page's generated Cordis API region into its Markdown content.
  902. * The page must contain exactly one `cordis-surface` marker region (the markers are
  903. * part of the hand-owned page skeleton once, then owned by the generator);
  904. * zero or several is a partition error the caller reports with the page path.
  905. * The match is on THIS generator's exact markers, not the generic region
  906. * grammar, so a page carrying only some other generator's region fails loud
  907. * instead of having that region overwritten.
  908. * @param content - the page's current full Markdown text.
  909. * @param region - the freshly rendered marker-delimited region.
  910. * @returns the page text with the region replaced.
  911. */
  912. export function spliceRegion(content: string, region: string): string {
  913. const lines = content.split('\n')
  914. const begins = lines.flatMap((line, index) => (line === REGION_BEGIN ? [index] : []))
  915. const ends = lines.flatMap((line, index) => (line === REGION_END ? [index] : []))
  916. if (begins.length !== 1 || ends.length !== 1) {
  917. throw new Error(`expected exactly 1 cordis-surface region, found ${begins.length} BEGIN/${ends.length} END; add the BEGIN/END cordis-surface markers once`)
  918. }
  919. const begin = begins[0] ?? -1
  920. const end = ends[0] ?? -1
  921. if (end < begin) throw new Error('cordis-surface END marker precedes its BEGIN')
  922. return [...lines.slice(0, begin), ...region.split('\n'), ...lines.slice(end + 1)].join('\n')
  923. }
  924. /** The declared-vs-rendered inputs {@link walkPartitionProblems} judges. */
  925. export interface WalkPartitionInput {
  926. /** Service key → source pointer, as the rendering projection produced them. */
  927. readonly renderedKeys: ReadonlyMap<string, string>
  928. /** Event scopes the rendering projection produced. */
  929. readonly renderedScopes: ReadonlySet<string>
  930. /** Event names the rendering projection produced. */
  931. readonly renderedEventNames: ReadonlySet<string>
  932. /** Context key → first declaring file, from the independent AST scan. */
  933. readonly declaredKeys: ReadonlyMap<string, string>
  934. /** Event name → first declaring file, from the independent AST scan. */
  935. readonly declaredEvents: ReadonlyMap<string, string>
  936. }
  937. /** The curated partition maps {@link walkPartitionProblems} enforces. */
  938. export interface WalkPartitionMaps {
  939. readonly servicePage: Readonly<Record<string, string>>
  940. readonly serviceWalkExemptions: Readonly<Record<string, string>>
  941. readonly eventScopePage: Readonly<Record<string, string>>
  942. readonly eventWalkExemptions: Readonly<Record<string, string>>
  943. }
  944. /** Project paired Markdown destinations in one generated region to the page's locale. */
  945. export function localizePageRegion(region: string, pageRel: string, scanRoot: string = root): string {
  946. if (!pageRel.endsWith('.zh.md')) return region
  947. const manifest = parseTranslationPairingManifest(
  948. readFileSync(resolve(scanRoot, 'scripts/translation-pairing.manifest.json'), 'utf8'),
  949. )
  950. return rewriteTranslationLinkLocales(region, {
  951. repoRoot: scanRoot,
  952. sourcePath: pageRel,
  953. isTranslationPairSource: translationPairSourcePredicate(manifest),
  954. }).content
  955. }
  956. /**
  957. * Judge the rendered API and the independent AST scan against the curated
  958. * partition maps, fail-closed in both directions for services AND events: a
  959. * rendered key/scope must be mapped to a page, a mapped key/scope must still
  960. * render, and — the backstop — a DECLARED key/event the projection cannot see
  961. * must carry a named walk exemption (a rendered one must not). A third
  962. * direction guards the scan itself: everything rendered must also be declared
  963. * to the scan, so a scan blind spot cannot decay silently. Pure so the
  964. * acceptance paths are provable without running the projection.
  965. * @param input - rendered API plus the declared-key/event scans.
  966. * @param maps - the curated page maps and walk exemptions.
  967. * @returns one message per violation, empty when the partition holds.
  968. */
  969. export function walkPartitionProblems(input: WalkPartitionInput, maps: WalkPartitionMaps): string[] {
  970. const problems: string[] = []
  971. for (const [key, source] of input.renderedKeys) {
  972. if (!Object.hasOwn(maps.servicePage, key)) problems.push(`service ctx.${key} (${source}) has no SERVICE_PAGE entry; every service maps to exactly one subsystems page.`)
  973. }
  974. for (const scope of [...input.renderedScopes].sort()) {
  975. if (!Object.hasOwn(maps.eventScopePage, scope)) problems.push(`event scope '${scope}/*' has no EVENT_SCOPE_PAGE entry; every event scope maps to exactly one subsystems page.`)
  976. }
  977. for (const key of Object.keys(maps.servicePage)) {
  978. if (!input.renderedKeys.has(key)) problems.push(`SERVICE_PAGE maps 'ctx.${key}' but the projection discovers no such service; remove the stale entry.`)
  979. }
  980. for (const scope of Object.keys(maps.eventScopePage)) {
  981. if (!input.renderedScopes.has(scope)) problems.push(`EVENT_SCOPE_PAGE maps '${scope}/*' but the projection discovers no such scope; remove the stale entry.`)
  982. }
  983. // The rendering projection only sees a Context key it can resolve to a
  984. // documented service class. The independent scan reads EVERY Context merge
  985. // so a key the projection cannot render must either be rendered (mapped) or
  986. // carry a named SERVICE_WALK_EXEMPTIONS reason — never vanish silently.
  987. for (const [key, rel] of input.declaredKeys) {
  988. const rendered = input.renderedKeys.has(key)
  989. const exempt = Object.hasOwn(maps.serviceWalkExemptions, key)
  990. if (!rendered && !exempt) {
  991. problems.push(`ctx.${key} (${rel}) is declared in a Context merge but invisible to the rendering projection; map it in SERVICE_PAGE (after making it renderable) or name it in SERVICE_WALK_EXEMPTIONS with its documentation owner.`)
  992. }
  993. if (rendered && exempt) problems.push(`ctx.${key} is rendered by the projection but still listed in SERVICE_WALK_EXEMPTIONS; remove the stale exemption.`)
  994. }
  995. for (const key of Object.keys(maps.serviceWalkExemptions)) {
  996. if (!input.declaredKeys.has(key)) problems.push(`SERVICE_WALK_EXEMPTIONS names 'ctx.${key}' but no Context merge declares it; remove the stale exemption.`)
  997. }
  998. // The event mirror of the service backstop: the projection walks only files
  999. // reachable from host-face package exports, so a client-face or unreachable
  1000. // Events merge would otherwise vanish without a trace.
  1001. for (const [name, rel] of input.declaredEvents) {
  1002. const rendered = input.renderedEventNames.has(name)
  1003. const exempt = Object.hasOwn(maps.eventWalkExemptions, name)
  1004. if (!rendered && !exempt) {
  1005. problems.push(`event '${name}' (${rel}) is declared in an Events merge but invisible to the rendering projection; make it renderable (mapped via EVENT_SCOPE_PAGE) or name it in EVENT_WALK_EXEMPTIONS with its documentation owner.`)
  1006. }
  1007. if (rendered && exempt) problems.push(`event '${name}' is rendered by the projection but still listed in EVENT_WALK_EXEMPTIONS; remove the stale exemption.`)
  1008. }
  1009. for (const name of Object.keys(maps.eventWalkExemptions)) {
  1010. if (!input.declaredEvents.has(name)) problems.push(`EVENT_WALK_EXEMPTIONS names '${name}' but no Events merge declares it; remove the stale exemption.`)
  1011. }
  1012. // Self-check the scan itself: everything the projection renders is declared
  1013. // in a Context/Events merge the scan must also reach, so a rendered key or
  1014. // event the scan cannot see means the SCAN regressed (glob, prefilter, or
  1015. // block walk) — a partial blind spot that exemption staleness alone would
  1016. // never appear.
  1017. for (const key of input.renderedKeys.keys()) {
  1018. if (!input.declaredKeys.has(key)) problems.push(`ctx.${key} is rendered by the projection but the independent scan finds no Context merge declaring it; the scan has a blind spot (glob, prefilter, or module-block walk) — fix the scan, not the maps.`)
  1019. }
  1020. for (const name of input.renderedEventNames) {
  1021. if (!input.declaredEvents.has(name)) problems.push(`event '${name}' is rendered by the projection but the independent scan finds no Events merge declaring it; the scan has a blind spot (glob, prefilter, or module-block walk) — fix the scan, not the maps.`)
  1022. }
  1023. return problems
  1024. }
  1025. /**
  1026. * Compute every generated artifact: the inherited-tier page, the model-facing
  1027. * runtime API module, plus, per mapped subsystems page, the pair's two updated
  1028. * documents with the injected region. Fail-loud partition checks live here: an
  1029. * unmapped service/event scope, a mapping whose page file does not exist, a
  1030. * curated entry whose key/scope the projection no longer discovers, a declared
  1031. * Context key or Events member the projection cannot see without a named walk
  1032. * exemption, and a mapped page missing its markers are all aggregated errors.
  1033. * @returns `[repo-relative path, exact content]` for every generated artifact.
  1034. */
  1035. export function computeOutputs(): [string, string][] {
  1036. const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY)
  1037. const services = [...model.services]
  1038. const events = [...model.events]
  1039. const declaredKeys = new Map<string, string>()
  1040. const declaredEvents = new Map<string, string>()
  1041. for (const { rel, sf, body } of contextMergeFiles(root, ['packages/*/*/src/**/*.ts', 'packages/*/*/src/**/*.tsx'])) {
  1042. for (const key of contextKeyMap(body, sf).keys()) {
  1043. if (!declaredKeys.has(key)) declaredKeys.set(key, rel)
  1044. }
  1045. for (const name of eventNameList(body, sf)) {
  1046. if (!declaredEvents.has(name)) declaredEvents.set(name, rel)
  1047. }
  1048. }
  1049. const problems = walkPartitionProblems({
  1050. renderedKeys: new Map(services.map(s => [s.key, s.source])),
  1051. renderedScopes: new Set(events.map(e => e.scope)),
  1052. renderedEventNames: new Set(events.map(e => e.name)),
  1053. declaredKeys,
  1054. declaredEvents,
  1055. }, {
  1056. servicePage: SERVICE_PAGE,
  1057. serviceWalkExemptions: SERVICE_WALK_EXEMPTIONS,
  1058. eventScopePage: EVENT_SCOPE_PAGE,
  1059. eventWalkExemptions: EVENT_WALK_EXEMPTIONS,
  1060. })
  1061. if (problems.length > 0) throw new Error(`gen-cordis-catalog: ${problems.length} partition violation(s):\n${problems.map(p => ` ${p}`).join('\n')}`)
  1062. const pages = [...new Set([...Object.values(SERVICE_PAGE), ...Object.values(EVENT_SCOPE_PAGE)])].sort()
  1063. const outputs: [string, string][] = [
  1064. [OUT_INHERITED, renderInheritedPage(CORDIS_CATALOG_POLICY)],
  1065. [OUT_RUNTIME_API, projector.renderRuntimeApi(model)],
  1066. ]
  1067. for (const page of pages) {
  1068. const region = renderPageRegion(
  1069. page,
  1070. services.filter(s => SERVICE_PAGE[s.key] === page),
  1071. events.filter(e => EVENT_SCOPE_PAGE[e.scope] === page),
  1072. CORDIS_CATALOG_POLICY,
  1073. )
  1074. for (const side of [page, page.replace(/\.md$/, '.zh.md')]) {
  1075. const rel = `${SUBSYSTEMS_DIR}/${side}`
  1076. const localizedRegion = localizePageRegion(region, rel)
  1077. let current: string
  1078. try {
  1079. current = readFileSync(resolve(root, rel), 'utf8')
  1080. } catch {
  1081. // Both pair sides must exist before a region can be injected; the
  1082. // pairing gate owns pair completeness, this generator names the miss.
  1083. problems.push(`${rel}: mapped subsystems page does not exist.`)
  1084. continue
  1085. }
  1086. try {
  1087. outputs.push([rel, spliceRegion(current, localizedRegion)])
  1088. } catch (error) {
  1089. problems.push(`${rel}: ${error instanceof Error ? error.message : String(error)}`)
  1090. }
  1091. }
  1092. }
  1093. if (problems.length > 0) throw new Error(`gen-cordis-catalog: ${problems.length} page violation(s):\n${problems.map(p => ` ${p}`).join('\n')}`)
  1094. return outputs
  1095. }
  1096. /**
  1097. * Re-record a pair's `.i18n.yaml` after a region write ONLY when the write is
  1098. * region-confined: both sides' region-stripped content must be byte-equal to
  1099. * the region-stripped previous content whose hashes the record holds. The
  1100. * caller supplies the previous bytes (read before writing); human-content
  1101. * drift leaves the record untouched so the pairing gate still demands the
  1102. * normal translation flow.
  1103. * @param pageRel - repo-relative English page path (`docs/subsystems/x.md`).
  1104. * @param before - pre-write bytes per repo-relative path.
  1105. * @param scanRoot - repository root override for tests.
  1106. * @returns true when the record was refreshed.
  1107. */
  1108. export function maybeRecordPair(pageRel: string, before: Map<string, Buffer>, scanRoot: string = root): boolean {
  1109. const zhRel = pageRel.replace(/\.md$/, '.zh.md')
  1110. const metaRel = pageRel.replace(/\.md$/, '.i18n.yaml')
  1111. const metaAbs = resolve(scanRoot, metaRel)
  1112. let meta: string
  1113. try {
  1114. meta = readFileSync(metaAbs, 'utf8')
  1115. } catch {
  1116. // No record yet: a brand-new pair is recorded by the author's --write
  1117. // after review, never silently by regeneration.
  1118. return false
  1119. }
  1120. // The record must contain exactly the two valid entries for THIS pair;
  1121. // a malformed or renamed-key sidecar is the pairing gate's problem to
  1122. // report, never something regeneration silently repairs into validity.
  1123. const recorded = parsePairMeta(meta)
  1124. const names = [pageRel, zhRel].map(rel => rel.split('/').at(-1) ?? rel)
  1125. if (!recorded || recorded.size !== 2 || !names.every(name => recorded.has(name))) return false
  1126. for (const rel of [pageRel, zhRel]) {
  1127. const previous = before.get(rel)
  1128. if (!previous) return false
  1129. if (recorded.get(rel.split('/').at(-1) ?? rel) !== blobHash(previous)) return false
  1130. const current = readFileSync(resolve(scanRoot, rel))
  1131. const strippedBefore = partitionGeneratedRegions(previous.toString('utf8')).stripped
  1132. const strippedAfter = partitionGeneratedRegions(current.toString('utf8')).stripped
  1133. if (strippedBefore !== strippedAfter) return false
  1134. }
  1135. const source = readFileSync(resolve(scanRoot, pageRel))
  1136. const zh = readFileSync(resolve(scanRoot, zhRel))
  1137. writeFileSync(metaAbs, renderPairMeta(pageRel, blobHash(source), zhRel, blobHash(zh)))
  1138. return true
  1139. }
  1140. /** CLI entry: default regenerates every artifact, `--check` fails if any is
  1141. * stale. Guarded behind an entry-point check so importing this module for
  1142. * tests neither regenerates the committed files nor calls process.exit.
  1143. * @returns nothing; writes files or reports freshness through the process.
  1144. */
  1145. export function main(): void {
  1146. const outputs: [string, string][] = [
  1147. ...computeOutputs(),
  1148. ...renderCordisCoreApiPages(),
  1149. ]
  1150. if (process.argv.includes('--check')) {
  1151. const stale: string[] = []
  1152. for (const [out, content] of outputs) {
  1153. let committed: string | null = null
  1154. try {
  1155. committed = readFileSync(resolve(root, out), 'utf8')
  1156. } catch {
  1157. // Only ENOENT (not yet generated) is expected; a present-but-unreadable
  1158. // file is not a state this repo produces. Either way the remedy is the
  1159. // same — regenerate — so treat a read failure as "stale".
  1160. committed = null
  1161. }
  1162. if (committed !== content) stale.push(out)
  1163. }
  1164. if (stale.length === 0) {
  1165. console.log(`gen-cordis-catalog: ${outputs.length} generated file(s)/region(s) are up to date.`)
  1166. process.exit(0)
  1167. }
  1168. console.error(`gen-cordis-catalog: stale — ${stale.join(', ')}. Run \`pnpm run gen-cordis-catalog\` and commit the result.`)
  1169. process.exit(1)
  1170. }
  1171. const before = new Map<string, Buffer>()
  1172. for (const [out] of outputs) {
  1173. try {
  1174. before.set(out, readFileSync(resolve(root, out)))
  1175. } catch {
  1176. // First generation of this artifact; nothing to guard, nothing to record.
  1177. }
  1178. }
  1179. let changedPages = 0
  1180. let recorded = 0
  1181. for (const [out, content] of outputs) {
  1182. const destination = resolve(root, out)
  1183. if (before.get(out)?.toString('utf8') === content) continue
  1184. mkdirSync(dirname(destination), { recursive: true })
  1185. writeFileSync(destination, content)
  1186. changedPages++
  1187. }
  1188. for (const page of [...new Set([...Object.values(SERVICE_PAGE), ...Object.values(EVENT_SCOPE_PAGE)])]) {
  1189. const rel = `${SUBSYSTEMS_DIR}/${page}`
  1190. const zhRel = rel.replace(/\.md$/, '.zh.md')
  1191. const wroteEither = [rel, zhRel].some((side) => {
  1192. const previous = before.get(side)
  1193. return previous !== undefined && previous.toString('utf8') !== readFileSync(resolve(root, side), 'utf8')
  1194. })
  1195. if (wroteEither && maybeRecordPair(rel, before)) recorded++
  1196. }
  1197. console.log(`gen-cordis-catalog: ${outputs.length} artifact(s) computed, ${changedPages} written, ${recorded} pair record(s) refreshed.`)
  1198. }
  1199. if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
  1200. main()
  1201. }