2026-07-12 02:12:36 +08:00
/**
2026-07-14 14:37:16 +08:00
* Doc-sync gate for package README Model Experience sections. It validates
2026-07-19 17:39:50 +08:00
* audited package classifications, model/token/KV-cache fields, package-owned
* text blocks, generated-catalog links, and final-section order. See the
2026-07-19 22:50:49 +08:00
* [Model Experience Agent Note](../.agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md).
2026-07-12 02:12:36 +08:00
*/
import { existsSync , globSync , readFileSync } from 'node:fs'
2026-07-06 02:28:44 +08:00
import { relative , resolve , sep } from 'node:path'
2026-07-14 14:01:35 +08:00
import { markdownHeadingLines , markdownProseLines , type MarkdownProseLine } from './markdown.ts'
2026-07-12 02:12:36 +08:00
const root = resolve ( import . meta . dirname , '..' )
const HEADING = '## Model Experience'
2026-07-12 02:55:26 +08:00
const LIMITATIONS_HEADING = '## Known Limitations and Deferred Work'
2026-07-19 18:08:42 +08:00
const MODEL_VIEW_HEADING = '#### What the model sees'
const TOKEN_EFFECT_HEADING = '#### Token effect'
const KV_CACHE_EFFECT_HEADING = '#### KV Cache effect'
const FIELD_HEADINGS = [ MODEL_VIEW_HEADING , TOKEN_EFFECT_HEADING , KV_CACHE_EFFECT_HEADING ] as const
2026-07-12 02:12:36 +08:00
2026-07-13 15:47:46 +08:00
type SentenceKind = 'none' | 'indirect'
interface SentenceContract {
kind : SentenceKind
reason : string
}
2026-07-14 11:22:53 +08:00
/**
2026-07-14 13:31:31 +08:00
* Generic packages whose public contract is model-agnostic. Their READMEs omit
* Model Experience entirely; the reason stays here as reviewable audit evidence
* so an absent section cannot be mistaken for forgotten documentation.
2026-07-14 11:22:53 +08:00
*/
2026-07-14 13:31:31 +08:00
const NO_MODEL_EXPERIENCE_SECTION : Readonly < Record < string , string > > = {
'packages/core/scope' : 'The package is a model-agnostic registration and lifecycle primitive; model-facing consumers own any context selection.' ,
2026-07-14 11:22:53 +08:00
'packages/util/brand' : 'The package is a type-only primitive erased at compile time.' ,
2026-07-14 19:50:25 +08:00
'packages/util/paths' : 'The package only resolves harness-owned host paths; model-facing consumers own any rendered use.' ,
2026-08-04 16:17:32 +08:00
'packages/util/environment' : 'The package only resolves host environment values; model-facing consumers own any rendered use.' ,
2026-07-14 11:22:53 +08:00
}
2026-07-13 15:47:46 +08:00
/**
2026-07-19 17:39:50 +08:00
* Packages whose Model Experience is simple enough for one gated sentence plus
2026-07-24 19:54:25 +08:00
* a KV-cache field. Every other package must carry canonical model-context
2026-07-19 17:39:50 +08:00
* blocks. A package moves on or off this list with its context behavior.
2026-07-13 15:47:46 +08:00
*/
const SENTENCE_MODEL_EXPERIENCE : Readonly < Record < string , SentenceContract > > = {
2026-07-23 15:20:47 +08:00
'packages/attachment/attachment' : { kind : 'indirect' , reason : 'The storage seam delegates model request rendering to provider adapters.' } ,
'packages/attachment/attachment-local' : { kind : 'indirect' , reason : 'The local backend delegates model request rendering to provider adapters.' } ,
2026-07-13 15:47:46 +08:00
'packages/bash/bash' : { kind : 'indirect' , reason : 'The service interface delegates all model rendering to dsh-tool-bash.' } ,
2026-07-24 19:54:25 +08:00
'packages/bash/bash-env' : { kind : 'indirect' , reason : 'The env service exposes managed DSH_* facts through the shell tools (dsh-tool-bash/dsh-tool-pwsh); it registers no prompt or schema of its own.' } ,
2026-07-13 22:40:19 +08:00
'packages/bash/bash-local' : { kind : 'indirect' , reason : 'The executor backend delegates model rendering to dsh-tool-bash.' } ,
2026-08-01 18:48:17 +08:00
'packages/bash/pwsh-local' : { kind : 'indirect' , reason : 'The executor backend delegates model rendering to dsh-tool-pwsh.' } ,
2026-07-13 15:47:46 +08:00
'packages/code-runtime/code-runtime' : { kind : 'indirect' , reason : 'The service interface delegates model rendering to Code Mode in dsh-tools.' } ,
2026-08-05 20:31:52 +08:00
'packages/core/agent-tool-mode' : { kind : 'indirect' , reason : 'The row only selects between the two projections dsh-tools owns; it registers no prompt, schema, or result of its own.' } ,
2026-07-13 22:40:19 +08:00
'packages/code-runtime/code-runtime-worker' : { kind : 'indirect' , reason : 'The worker backend delegates model rendering to Code Mode in dsh-tools.' } ,
2026-08-04 00:25:19 +08:00
'packages/client/ui-agent-preset' : { kind : 'indirect' , reason : 'Browser-side settings row; the preset it selects owns every model-facing effect.' } ,
2026-08-09 12:13:58 +08:00
'packages/core/agent-default-model' : { kind : 'indirect' , reason : 'The service supplies a ModelSelection; request assembly and adapters own the model-visible request.' } ,
2026-08-03 20:30:33 +08:00
'packages/preset/agent-presets' : { kind : 'indirect' , reason : 'The mount installs a preset\'s own plugins, which own every model-facing registration it makes visible.' } ,
2026-07-28 23:47:57 +08:00
'packages/typert/registry' : { kind : 'none' , reason : 'Runtime type registry; consumers (cordis_inspect, wire faces, gates) own any model-visible projection of registry contents.' } ,
'packages/typert/loader' : { kind : 'none' , reason : 'Loader integration only registers generated artifacts; consumers own any model-visible projection.' } ,
2026-07-28 10:05:30 +08:00
'packages/e2b/e2b' : { kind : 'none' , reason : 'The shared remote-runtime owner registers no model context; provider adapters and consumers own rendered effects.' } ,
2026-07-24 19:54:25 +08:00
'packages/client/hmr' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/modules' : { kind : 'none' , reason : 'Browser-side module-loading kernel machinery; registers nothing model-facing.' } ,
'packages/client/test-runtime' : { kind : 'none' , reason : 'Browser-side test infrastructure (jsdom bench); registers nothing model-facing.' } ,
'packages/client/ui-slots' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
2026-08-11 17:01:29 +08:00
'packages/client/ui-attachment' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
2026-07-24 19:54:25 +08:00
'packages/client/ui-primitives' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/web-react' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/schema-form' : { kind : 'none' , reason : 'Browser-side form-rendering library; registers nothing model-facing.' } ,
'packages/client/connection' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
2026-08-07 15:48:29 +08:00
'packages/api/remotes' : { kind : 'none' , reason : 'The Remote BFF selects business methods and identity policy; selected services own any model-visible effect.' } ,
2026-07-24 19:54:25 +08:00
'packages/client/runtime' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-layout' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-sidebar' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-conversation' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
2026-08-08 15:33:51 +08:00
'packages/client/ui-tool' : { kind : 'none' , reason : 'Browser-side Tool presentation layer; renders logged calls without changing model context.' } ,
2026-07-24 19:54:25 +08:00
'packages/client/ui-deliverables' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-task' : { kind : 'none' , reason : 'Browser-side read-only projection of ctx.tasks records; dsh-tool-tasks owns the model-facing behavior.' } ,
2026-08-10 18:37:30 +08:00
'packages/client/ui-workflow-run' : { kind : 'none' , reason : 'Browser-side UI plugin layer; renders durable workflow records without changing model context.' } ,
2026-07-24 19:54:25 +08:00
'packages/client/ui-slash' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
2026-07-27 03:17:52 +08:00
'packages/client/ui-command' : { kind : 'indirect' , reason : 'The dispatch paths trigger the host command.execute RPC; each command handler\'s host package owns any model-visible effect.' } ,
2026-08-09 12:13:58 +08:00
'packages/client/ui-model' : { kind : 'indirect' , reason : 'Selection routes session.selectModel; the Host snapshots the selection at the next prompt-assembly boundary and owns the model-visible effect.' } ,
2026-07-28 23:34:19 +08:00
'packages/client/ui-goal' : { kind : 'indirect' , reason : 'The strip verbs route goal.* mutations; the host GoalService owns the model-visible goal/change context message.' } ,
2026-07-28 23:55:00 +08:00
'packages/client/ui-permission' : { kind : 'indirect' , reason : 'The picker submits the host /permission command; the knob events it appends own the model-visible effect through the sandbox/approval consumers.' } ,
2026-08-10 19:14:59 +08:00
'packages/client/ui-plugin-config' : { kind : 'none' , reason : 'Browser-side settings surface; registers no model surface.' } ,
2026-07-28 23:39:50 +08:00
'packages/client/ui-plan' : { kind : 'indirect' , reason : 'The chip dispatches /plan off; dsh-plan-mode owns the model-visible policy, exit tool, and logged state.' } ,
2026-07-22 23:39:50 +08:00
'packages/client/ui-question' : { kind : 'indirect' , reason : 'The package mounts dsh-tool-ask-user; that tool owns the model-visible schema and answer rendering.' } ,
2026-07-24 19:54:25 +08:00
'packages/client/ui-trajectory' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-workspace' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-theme' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-settings' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-settings-general' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/ui-models' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/locale' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
'packages/client/web' : { kind : 'none' , reason : 'Browser-side UI plugin layer; registers nothing model-facing.' } ,
2026-07-15 15:57:57 +08:00
'packages/examples/agent-spine-demo' : { kind : 'indirect' , reason : 'The bundle only mounts model-facing child plugins.' } ,
2026-07-13 15:47:46 +08:00
'packages/fs/fs' : { kind : 'indirect' , reason : 'The service interface delegates model rendering to dsh-tool-fs.' } ,
2026-07-28 14:52:37 +08:00
'packages/e2b/fs-e2b' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-tool-fs.' } ,
2026-07-13 22:40:19 +08:00
'packages/fs/fs-local' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-tool-fs.' } ,
2026-07-13 15:47:46 +08:00
'packages/hooks/hook-protocol' : { kind : 'indirect' , reason : 'Only the hook bridge plugins render decoded hook output to a model.' } ,
2026-07-24 19:54:25 +08:00
'packages/host/apiproxy' : { kind : 'none' , reason : 'The wire contract and fetch carriers move already-composed messages and register nothing model-facing.' } ,
'packages/host/directory-picker' : { kind : 'none' , reason : 'The GUI-host picking seam registers nothing model-facing.' } ,
'packages/host/directory-picker-auto' : { kind : 'none' , reason : 'The GUI-host picking chooser only mounts a backend row; it registers nothing model-facing.' } ,
'packages/host/directory-picker-browse' : { kind : 'none' , reason : 'The GUI-host picking backend registers nothing model-facing.' } ,
'packages/host/directory-picker-native' : { kind : 'none' , reason : 'The GUI-host picking backend registers nothing model-facing.' } ,
'packages/host/webserver' : { kind : 'none' , reason : 'The HTTP carrier bridges browser and API handler and registers nothing model-facing.' } ,
'packages/host/frontend-static' : { kind : 'none' , reason : 'The SPA dist server answers browser asset requests and registers nothing model-facing.' } ,
'packages/bundle/base' : { kind : 'indirect' , reason : 'The bundle is a patch-list carrier; each inserted row\'s package owns its model-facing behavior.' } ,
2026-08-09 12:13:58 +08:00
'packages/bundle/headless' : { kind : 'none' , reason : 'The one-shot runner submits the task as an ordinary user message; prompts and tools belong to the composed base and headless bundles.' } ,
2026-07-14 00:22:52 +08:00
'packages/llm/llm' : { kind : 'none' , reason : 'The adapter registry forwards already-assembled requests unchanged.' } ,
2026-07-15 14:47:29 +08:00
'packages/llm/token-meter' : { kind : 'indirect' , reason : 'The measurement service leaves model-visible changes to its consumers.' } ,
2026-07-16 12:05:35 +08:00
'packages/lsp/lsp' : { kind : 'indirect' , reason : 'The provider registry delegates model rendering to dsh-tool-lsp.' } ,
'packages/lsp/lsp-local' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-tool-lsp.' } ,
2026-07-26 12:43:14 +08:00
'packages/subprocess/subprocess' : { kind : 'indirect' , reason : 'The seam delegates all model rendering to consumer seams such as the bash executor family.' } ,
2026-07-28 14:52:37 +08:00
'packages/e2b/subprocess-e2b' : { kind : 'indirect' , reason : 'The remote spawn backend delegates model rendering to consumer seams such as the bash executor family.' } ,
2026-07-26 12:43:14 +08:00
'packages/subprocess/subprocess-local' : { kind : 'indirect' , reason : 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' } ,
2026-07-13 22:40:19 +08:00
'packages/sandbox/sandbox-local' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' } ,
2026-08-08 02:26:26 +08:00
'packages/sandbox/sandbox-windows-acl' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to the bash/pwsh sandbox executors and their tools.' } ,
2026-08-11 15:56:06 +08:00
'packages/sdk/client' : { kind : 'none' , reason : 'Client-process library; model-facing behavior lives in the spawned runtime\'s composed plugins.' } ,
'packages/sdk/protocol' : { kind : 'none' , reason : 'Client-facing wire library; the runtime plugins behind the serving entry own model-facing behavior.' } ,
2026-07-24 19:54:25 +08:00
'packages/session/session-projection' : { kind : 'none' , reason : 'The projection registry serves client-facing read models of already-logged session state and registers nothing model-facing.' } ,
'packages/session/session-projection-cache' : { kind : 'none' , reason : 'The persisted cache accelerates host-side cold reads of projection state and registers nothing model-facing.' } ,
'packages/session-query/session-query' : { kind : 'none' , reason : 'The trusted query service exposes cloned records only to callers and registers nothing model-facing.' } ,
'packages/session-query/session-query-sqlite' : { kind : 'none' , reason : 'The search backend returns hits only to callers and registers nothing model-facing.' } ,
'packages/settings/settings' : { kind : 'indirect' , reason : 'The seam stores and resolves user settings; consumer plugins own any model-facing content fed by a value.' } ,
'packages/settings/settings-local' : { kind : 'indirect' , reason : 'The file provider stores and publishes namespace sections; consumers of ctx.settings own any model-facing behavior.' } ,
'packages/credentials/credentials' : { kind : 'indirect' , reason : 'The seam resolves credential references; the consuming adapter owns every model-facing use a value authorizes.' } ,
'packages/credentials/credentials-local' : { kind : 'indirect' , reason : 'The file/environment provider stores credential values; consumers of ctx.credentials own any model-facing behavior.' } ,
'packages/util/atomic-write' : { kind : 'none' , reason : 'Pure filesystem write primitive; registers nothing model-facing.' } ,
'packages/session/session-telemetry' : { kind : 'none' , reason : 'The seam observes the session stream and hands redacted copies outward; it registers nothing model-facing.' } ,
'packages/session/session-telemetry-otel' : { kind : 'none' , reason : 'The backend forwards seam records into the OTel SDK pipeline and registers nothing model-facing.' } ,
2026-08-11 12:39:04 +08:00
'packages/session/user-id' : { kind : 'none' , reason : 'The shared identifier reaches DeepSeek only as model-hidden HTTP metadata; it registers nothing model-facing.' } ,
2026-07-13 15:47:46 +08:00
'packages/skill/skill' : { kind : 'indirect' , reason : 'The provider registry delegates model rendering to dsh-tool-skill.' } ,
2026-08-05 21:50:44 +08:00
'packages/skill/skill-badge' : { kind : 'indirect' , reason : 'The bundled provider delegates model rendering to dsh-tool-skill.' } ,
2026-07-13 22:40:19 +08:00
'packages/skill/skill-local' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-tool-skill.' } ,
2026-07-17 18:21:54 +08:00
'packages/spill/spill' : { kind : 'indirect' , reason : 'The storage seam delegates model rendering to spill consumers.' } ,
'packages/spill/spill-local' : { kind : 'indirect' , reason : 'The storage backend delegates model rendering to spill consumers.' } ,
2026-07-13 15:47:46 +08:00
'packages/support/acp-snapshot' : { kind : 'none' , reason : 'The test harness observes and normalizes transcripts without changing live requests.' } ,
2026-07-16 17:39:53 +08:00
'packages/support/agent-loop-testkit' : { kind : 'none' , reason : 'The test helper mounts services but neither drives nor modifies model requests.' } ,
2026-07-13 15:47:46 +08:00
'packages/support/invariants' : { kind : 'none' , reason : 'The observer validates requests but never rewrites their context.' } ,
2026-08-08 15:06:01 +08:00
'packages/support/loader-smoke' : { kind : 'none' , reason : 'The test harness submits an ordinary user task but delegates prompt and tool composition to the loaded tree.' } ,
2026-07-25 08:20:40 +08:00
'packages/support/llm-mock-server' : { kind : 'none' , reason : 'The test server substitutes provider wire behavior without invoking a real model.' } ,
2026-07-14 00:22:52 +08:00
'packages/support/llm-replay' : { kind : 'none' , reason : 'The keyless adapter invokes no provider model.' } ,
2026-08-07 15:48:29 +08:00
'packages/api/gateway' : { kind : 'none' , reason : 'Remote dispatch infrastructure; invoked business methods own any model-visible effect.' } ,
2026-07-24 19:54:25 +08:00
'packages/typert/type-meta' : { kind : 'none' , reason : 'Compiler-independent Remote protocol declarations; registers nothing model-facing.' } ,
2026-07-28 23:47:57 +08:00
'packages/typert/generator' : { kind : 'none' , reason : 'The build-time generator runs outside any agent runtime and touches no model request.' } ,
2026-07-24 19:54:25 +08:00
'packages/tasks/tasks' : { kind : 'indirect' , reason : 'Producer and controller plugins own all model rendering over the task registry.' } ,
2026-07-26 05:13:39 +08:00
'packages/tasks/tasks-local' : { kind : 'indirect' , reason : 'The registry backend delegates model rendering to producer plugins and dsh-tool-tasks.' } ,
2026-07-15 15:57:57 +08:00
'packages/examples/acp-demo' : { kind : 'indirect' , reason : 'The app bundle delegates request composition to dsh-agent-spine-demo and dsh-acp.' } ,
2026-07-30 03:13:49 +08:00
'packages/boot/app-boot' : { kind : 'indirect' , reason : 'Only the loaded plugin tree contributes model context.' } ,
2026-08-06 20:52:26 +08:00
'packages/boot/cmdline' : { kind : 'none' , reason : 'Resolves the process command line before any session exists; configured rows own every model-visible consequence.' } ,
2026-07-15 15:57:57 +08:00
'packages/examples/jsonrpc-demo' : { kind : 'indirect' , reason : 'Only the externally configured plugin tree contributes model context.' } ,
2026-07-30 03:13:49 +08:00
'packages/interaction/permission' : { kind : 'indirect' , reason : 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' } ,
'packages/interaction/user-interaction' : { kind : 'indirect' , reason : 'Model-facing consumers render provider answers and seam errors.' } ,
2026-07-13 15:47:46 +08:00
'packages/util/timeout' : { kind : 'indirect' , reason : 'Only timeout consumers render timeout outcomes.' } ,
2026-07-17 18:21:54 +08:00
'packages/util/retention' : { kind : 'indirect' , reason : 'Only retention consumers render retained content and omission metadata.' } ,
2026-07-24 19:54:25 +08:00
'packages/util/native-command' : { kind : 'none' , reason : 'The host-side subprocess runner registers nothing model-facing.' } ,
2026-07-13 22:40:19 +08:00
'packages/web/web' : { kind : 'indirect' , reason : 'The provider registry delegates model rendering to dsh-tool-web.' } ,
'packages/web/web-fetch-local' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-tool-web.' } ,
'packages/web/web-search-exa' : { kind : 'indirect' , reason : 'The provider backend delegates model rendering to dsh-tool-web.' } ,
2026-07-13 15:47:46 +08:00
'packages/workflow/workflow' : { kind : 'indirect' , reason : 'The service delegates parent and child model rendering to its consumer and engine.' } ,
}
2026-07-12 02:12:36 +08:00
interface Failure {
path : string
message : string
}
2026-07-14 00:39:48 +08:00
type Line = MarkdownProseLine
2026-07-13 15:00:47 +08:00
2026-07-24 19:54:25 +08:00
interface ModelExperienceEntry {
2026-07-14 00:22:52 +08:00
heading : Line
modelView : Line
tokenEffect : Line
2026-07-19 17:39:50 +08:00
kvCacheEffect : Line
2026-07-14 00:22:52 +08:00
title : string
2026-07-19 18:08:42 +08:00
modelViewVerbatimBlocks : number
2026-07-14 00:22:52 +08:00
verbatimBlocks : number
}
2026-07-19 18:08:42 +08:00
interface ParsedField {
value : Line
verbatimBlocks : number
}
/** Validate H5-plus-markdown literals nested under one Model Experience field. */
function validateNestedVerbatim ( raw : readonly string [ ] , fragments : Set < string > ) : { blocks : number ; error? : string } {
2026-07-13 21:33:23 +08:00
let cursor = 0
while ( raw [ cursor ] ? . trim ( ) . length === 0 ) cursor += 1
2026-07-14 00:22:52 +08:00
if ( cursor === raw . length ) return { blocks : 0 }
2026-07-13 21:33:23 +08:00
let blocks = 0
while ( true ) {
while ( raw [ cursor ] ? . trim ( ) . length === 0 ) cursor += 1
if ( cursor === raw . length ) break
2026-07-19 18:08:42 +08:00
if ( ! /^##### \S/ . test ( raw [ cursor ] ? ? '' ) ) {
return { blocks , error : 'content after a field paragraph must be a titled H5 verbatim block' }
2026-07-13 21:33:23 +08:00
}
2026-07-19 18:08:42 +08:00
const title = ( raw [ cursor ] as string ) . slice ( '##### ' . length )
2026-07-13 21:33:23 +08:00
const fragment = headingFragment ( title )
2026-07-19 18:08:42 +08:00
if ( fragment . length === 0 ) return { blocks , error : 'verbatim H5 title must be non-empty' }
2026-07-14 00:22:52 +08:00
if ( fragments . has ( fragment ) ) {
2026-07-24 19:54:25 +08:00
return { blocks , error : ` verbatim H5 title ${ JSON . stringify ( title ) } is duplicated within its model-context entry ` }
2026-07-13 21:33:23 +08:00
}
2026-07-14 00:22:52 +08:00
fragments . add ( fragment )
2026-07-13 21:33:23 +08:00
cursor += 1
while ( raw [ cursor ] ? . trim ( ) . length === 0 ) cursor += 1
2026-07-13 22:26:33 +08:00
if ( raw [ cursor ] !== '```markdown' ) {
2026-07-19 18:08:42 +08:00
return { blocks , error : 'each nested verbatim H5 requires an exact ```markdown fence' }
2026-07-13 21:33:23 +08:00
}
cursor += 1
const contentStart = cursor
while ( cursor < raw . length && raw [ cursor ] !== '```' ) cursor += 1
2026-07-14 00:22:52 +08:00
if ( cursor === raw . length ) return { blocks , error : 'unterminated nested ```markdown fence' }
if ( cursor === contentStart ) return { blocks , error : 'nested ```markdown fence must not be empty' }
2026-07-13 21:33:23 +08:00
cursor += 1
blocks += 1
}
2026-07-14 00:22:52 +08:00
return { blocks }
2026-07-13 21:33:23 +08:00
}
2026-08-09 15:27:21 +08:00
/** GitHub-style fragment for the simple ASCII nested titles allowed by these rules. */
2026-07-13 21:33:23 +08:00
function headingFragment ( title : string ) : string {
return title . toLowerCase ( ) . replaceAll ( '`' , '' ) . replaceAll ( /[^a-z0-9 _-]/g , '' ) . trim ( ) . replaceAll ( /\s+/g , '-' )
}
2026-08-09 15:27:21 +08:00
/** A direct stable system-prompt contribution, as named by the README rules. */
2026-07-24 19:54:25 +08:00
function isDirectSystemPromptEntry ( title : string ) : boolean {
2026-07-14 00:22:52 +08:00
return /\bsystem prompt\b/i . test ( title )
}
/** Anchored generated-catalog links in one model-view field. */
function toolCatalogLinkFragments ( text : string ) : string [ ] {
return [ . . . text . matchAll ( /\]\(\.\.\/\.\.\/\.\.\/docs\/tool-catalog\.md#([a-z0-9_-]+)\)/g ) ]
. map ( match = > match [ 1 ] as string )
}
const toolCatalogFragments = new Set < string > ( )
for ( const line of readFileSync ( resolve ( root , 'docs/tool-catalog.md' ) , 'utf8' ) . split ( '\n' ) ) {
const title = /^## (.+)$/ . exec ( line ) ? . [ 1 ]
if ( title !== undefined ) toolCatalogFragments . add ( headingFragment ( title ) )
}
2026-07-12 02:12:36 +08:00
const failures : Failure [ ] = [ ]
2026-07-06 02:28:44 +08:00
const packageJsons = globSync ( 'packages/*/*/package.json' , { cwd : root } ) . map ( path = > path . split ( sep ) . join ( '/' ) ) . sort ( )
2026-07-13 15:47:46 +08:00
const scannedPackages = new Set ( packageJsons . map ( path = > path . slice ( 0 , - '/package.json' . length ) ) )
2026-07-13 22:26:33 +08:00
let structuredCount = 0
2026-07-24 19:54:25 +08:00
let modelContextEntryCount = 0
2026-07-14 13:31:31 +08:00
let omittedSectionCount = 0
2026-07-14 11:22:53 +08:00
let explainedNoneCount = 0
2026-07-13 15:47:46 +08:00
let indirectCount = 0
2026-07-13 21:33:23 +08:00
let verbatimBlockCount = 0
2026-07-24 19:54:25 +08:00
let systemPromptEntryCount = 0
let toolSchemaEntryCount = 0
2026-07-19 17:39:50 +08:00
let kvCacheEffectCount = 0
2026-07-13 15:47:46 +08:00
2026-07-14 13:31:31 +08:00
for ( const [ pkg , reason ] of Object . entries ( NO_MODEL_EXPERIENCE_SECTION ) ) {
2026-07-14 11:22:53 +08:00
if ( ! scannedPackages . has ( pkg ) ) {
2026-07-14 13:31:31 +08:00
failures . push ( { path : ` ${ pkg } /README.md ` , message : 'no-section allowlist entry does not name a scanned package' } )
2026-07-14 11:22:53 +08:00
}
if ( reason . trim ( ) . length === 0 ) {
2026-07-14 13:31:31 +08:00
failures . push ( { path : ` ${ pkg } /README.md ` , message : 'no-section allowlist entry must retain its audit justification' } )
2026-07-14 11:22:53 +08:00
}
if ( SENTENCE_MODEL_EXPERIENCE [ pkg ] !== undefined ) {
2026-07-14 13:31:31 +08:00
failures . push ( { path : ` ${ pkg } /README.md ` , message : 'package cannot appear in both Model Experience allowlists' } )
2026-07-14 11:22:53 +08:00
}
}
2026-07-13 15:47:46 +08:00
for ( const [ pkg , contract ] of Object . entries ( SENTENCE_MODEL_EXPERIENCE ) ) {
if ( ! scannedPackages . has ( pkg ) ) {
failures . push ( { path : ` ${ pkg } /README.md ` , message : 'sentence allowlist entry does not name a scanned package' } )
}
if ( contract . reason . trim ( ) . length === 0 ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : ` ${ pkg } /README.md ` , message : 'sentence allowlist entry must justify why structured model-context entries are unnecessary' } )
2026-07-13 15:47:46 +08:00
}
}
2026-07-12 02:12:36 +08:00
for ( const packageJson of packageJsons ) {
2026-07-13 15:47:46 +08:00
const pkg = packageJson . slice ( 0 , - '/package.json' . length )
2026-07-12 02:12:36 +08:00
const readme = packageJson . replace ( /package\.json$/ , 'README.md' )
const abs = resolve ( root , readme )
if ( ! existsSync ( abs ) ) {
2026-07-14 13:31:31 +08:00
failures . push ( { path : readme , message : 'missing package README' } )
2026-07-12 02:12:36 +08:00
continue
}
2026-07-13 21:33:23 +08:00
const text = readFileSync ( abs , 'utf8' )
const rawLines = text . split ( '\n' )
2026-07-14 00:39:48 +08:00
const lines = markdownProseLines ( text )
2026-07-14 14:01:35 +08:00
const headings = markdownHeadingLines ( text )
const h2Headings = headings . filter ( heading = > heading . depth === 2 )
const modelExperienceHeadings = headings . filter ( heading = > heading . text
. trim ( ) . replaceAll ( /\s+/g , ' ' ) . toLowerCase ( ) === 'model experience' )
const modelHeadings = modelExperienceHeadings . filter ( heading = > heading . depth === 2 && heading . raw === HEADING )
2026-07-14 13:31:31 +08:00
if ( NO_MODEL_EXPERIENCE_SECTION [ pkg ] !== undefined ) {
2026-07-14 14:01:35 +08:00
if ( modelExperienceHeadings . length !== 0 ) {
for ( const heading of modelExperienceHeadings ) {
failures . push ( { path : readme , message : ` line ${ heading . index } : audited model-agnostic package must omit every Model Experience heading; found ${ JSON . stringify ( heading . raw ) } ` } )
}
2026-07-14 13:31:31 +08:00
} else {
omittedSectionCount += 1
}
continue
}
2026-07-14 14:01:35 +08:00
const nonCanonicalModelHeading = modelExperienceHeadings . find ( heading = > heading . depth !== 2 || heading . raw !== HEADING )
if ( nonCanonicalModelHeading !== undefined ) {
failures . push ( { path : readme , message : ` line ${ nonCanonicalModelHeading . index } : non-canonical Model Experience heading ${ JSON . stringify ( nonCanonicalModelHeading . raw ) } ; use exactly ${ JSON . stringify ( HEADING ) } ` } )
continue
}
const modelHeading = modelHeadings . at ( 0 )
if ( modelHeading === undefined ) {
2026-07-12 02:12:36 +08:00
failures . push ( {
path : readme ,
2026-07-14 14:01:35 +08:00
message : ` missing ${ HEADING } ` ,
2026-07-12 02:12:36 +08:00
} )
continue
}
2026-07-14 14:01:35 +08:00
if ( modelHeadings . length !== 1 ) {
failures . push ( { path : readme , message : ` contains ${ modelHeadings . length } copies of ${ HEADING } ` } )
continue
}
2026-07-13 15:00:47 +08:00
const modelH2Index = h2Headings . indexOf ( modelHeading )
2026-07-14 14:01:35 +08:00
const limitationsH2Index = h2Headings . findIndex ( heading = > heading . depth === 2 && heading . raw === LIMITATIONS_HEADING )
2026-07-12 02:55:26 +08:00
if ( limitationsH2Index >= 0 ) {
if ( modelH2Index !== h2Headings . length - 2 || limitationsH2Index !== h2Headings . length - 1 ) {
failures . push ( {
path : readme ,
message : ` ${ HEADING } and ${ LIMITATIONS_HEADING } must be the final two H2 sections, in that order ` ,
} )
continue
}
} else if ( modelH2Index !== h2Headings . length - 1 ) {
failures . push ( { path : readme , message : ` ${ HEADING } must be the final H2 when ${ LIMITATIONS_HEADING } is absent ` } )
continue
}
2026-07-14 14:01:35 +08:00
const modelHeadingAt = lines . findIndex ( line = > line . index === modelHeading . index )
const body = lines . slice ( modelHeadingAt + 1 )
const h2Lines = new Set ( h2Headings . map ( heading = > heading . index ) )
const nextH2 = body . findIndex ( line = > h2Lines . has ( line . index ) )
2026-07-13 15:00:47 +08:00
const section = nextH2 < 0 ? body : body.slice ( 0 , nextH2 )
2026-07-13 21:33:23 +08:00
const nextH2Line = nextH2 < 0 ? rawLines . length + 1 : ( body [ nextH2 ] as Line ) . index
const rawSection = rawLines . slice ( modelHeading . index , nextH2Line - 1 )
2026-07-13 15:47:46 +08:00
const content = section . filter ( line = > line . raw . trim ( ) . length > 0 )
const sentenceContract = SENTENCE_MODEL_EXPERIENCE [ pkg ]
if ( sentenceContract !== undefined ) {
const pattern = sentenceContract . kind === 'none' ? /^None, as .+\.$/ : /^Indirectly, through .+\.$/
2026-07-13 21:33:23 +08:00
const rawContent = rawSection . filter ( line = > line . trim ( ) . length > 0 )
2026-07-19 17:39:50 +08:00
const sentence = content [ 0 ]
2026-07-19 18:08:42 +08:00
const kvCacheHeading = content [ 1 ]
const kvCacheEffect = content [ 2 ]
if ( content . length !== 3 || rawContent . length !== 3 || ! pattern . test ( sentence ? . raw ? ? '' ) ) {
2026-07-13 15:47:46 +08:00
const prefix = sentenceContract . kind === 'none' ? 'None, as ' : 'Indirectly, through '
2026-07-19 18:08:42 +08:00
failures . push ( { path : readme , message : ` must contain exactly one sentence beginning ${ JSON . stringify ( prefix ) } and ending with a period, followed by ${ KV_CACHE_EFFECT_HEADING } and one non-empty paragraph ` } )
2026-07-19 17:39:50 +08:00
continue
}
2026-07-19 18:08:42 +08:00
if ( kvCacheHeading ? . raw !== KV_CACHE_EFFECT_HEADING
|| kvCacheEffect === undefined
|| /^#{1,6} / . test ( kvCacheEffect . raw )
|| kvCacheEffect . raw . trim ( ) . length === 0 ) {
failures . push ( { path : readme , message : ` line ${ kvCacheHeading ? . index ? ? sentence ? . index ? ? modelHeading . index } : short Model Experience form requires exact ${ KV_CACHE_EFFECT_HEADING } and one non-empty paragraph ` } )
2026-07-19 17:39:50 +08:00
continue
}
2026-07-19 18:08:42 +08:00
if ( sentence === undefined
|| sentence . index !== modelHeading . index + 2
|| kvCacheHeading . index !== sentence . index + 2
|| kvCacheEffect . index !== kvCacheHeading . index + 2 ) {
failures . push ( { path : readme , message : 'short Model Experience sentence, KV-cache H4, and paragraph require one blank line between each element' } )
2026-07-13 15:47:46 +08:00
continue
}
2026-07-14 11:22:53 +08:00
if ( sentenceContract . kind === 'none' ) explainedNoneCount += 1
2026-07-13 15:47:46 +08:00
else indirectCount += 1
2026-07-19 17:39:50 +08:00
kvCacheEffectCount += 1
2026-07-13 15:47:46 +08:00
continue
}
2026-07-14 11:22:53 +08:00
const shortSentence = content . find ( line = > line . raw === 'None.' || /^None, as |^Indirectly, through / . test ( line . raw ) )
2026-07-13 15:47:46 +08:00
if ( shortSentence !== undefined ) {
2026-07-14 13:31:31 +08:00
failures . push ( { path : readme , message : ` line ${ shortSentence . index } : short Model Experience form requires an audited entry in SENTENCE_MODEL_EXPERIENCE ` } )
2026-07-13 15:47:46 +08:00
continue
}
2026-07-24 19:54:25 +08:00
const entryStarts = content
2026-07-14 00:22:52 +08:00
. map ( ( line , index ) = > ( { line , index } ) )
. filter ( entry = > /^### \S/ . test ( entry . line . raw ) )
2026-07-24 19:54:25 +08:00
if ( entryStarts . length === 0 || entryStarts [ 0 ] ? . index !== 0 ) {
failures . push ( { path : readme , message : 'must contain one or more complete model-context entries' } )
2026-07-12 02:12:36 +08:00
continue
}
2026-07-24 19:54:25 +08:00
const modelContextEntries : ModelExperienceEntry [ ] = [ ]
const entryFragments = new Set < string > ( )
let entryError = false
for ( let entryIndex = 0 ; entryIndex < entryStarts . length ; entryIndex += 1 ) {
const start = entryStarts [ entryIndex ] as { line : Line ; index : number }
const end = entryStarts [ entryIndex + 1 ] ? . index ? ? content . length
2026-07-14 00:22:52 +08:00
const entries = content . slice ( start . index , end )
const heading = entries [ 0 ] as Line
const title = heading . raw . slice ( '### ' . length )
const fragment = headingFragment ( title )
2026-07-13 22:26:33 +08:00
if ( fragment . length === 0 ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : ` line ${ heading . index } : each model-context entry requires a non-empty H3 heading ` } )
entryError = true
2026-07-13 22:26:33 +08:00
break
2026-07-12 02:12:36 +08:00
}
2026-07-24 19:54:25 +08:00
if ( entryFragments . has ( fragment ) ) {
failures . push ( { path : readme , message : ` line ${ heading . index } : duplicate model-context entry link fragment ${ JSON . stringify ( fragment ) } ` } )
entryError = true
2026-07-13 22:26:33 +08:00
break
}
2026-07-19 18:08:42 +08:00
const fieldStarts = entries
. map ( ( line , index ) = > ( { line , index } ) )
. filter ( entry = > /^#### \S/ . test ( entry . line . raw ) )
if ( fieldStarts . length !== FIELD_HEADINGS . length || fieldStarts [ 0 ] ? . index !== 1 ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : ` line ${ heading . index } : model-context entry requires exactly three ordered H4 fields: ${ FIELD_HEADINGS . join ( ', ' ) } ` } )
entryError = true
2026-07-13 22:26:33 +08:00
break
}
2026-07-24 19:54:25 +08:00
if ( ( entryIndex === 0 && heading . index !== modelHeading . index + 2 )
2026-07-14 00:22:52 +08:00
|| rawLines [ heading . index - 2 ] ? . trim ( ) . length !== 0
2026-07-19 18:08:42 +08:00
|| fieldStarts [ 0 ] . line . index !== heading . index + 2 ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : ` line ${ heading . index } : model-context entry heading and first field require one blank line between them ` } )
entryError = true
2026-07-14 00:22:52 +08:00
break
}
2026-07-19 18:08:42 +08:00
const parsedFields : ParsedField [ ] = [ ]
const verbatimFragments = new Set < string > ( )
for ( let fieldIndex = 0 ; fieldIndex < FIELD_HEADINGS . length ; fieldIndex += 1 ) {
const fieldStart = fieldStarts [ fieldIndex ] as { line : Line ; index : number }
const expectedHeading = FIELD_HEADINGS [ fieldIndex ] as string
if ( fieldStart . line . raw !== expectedHeading ) {
failures . push ( { path : readme , message : ` line ${ fieldStart . line . index } : expected exact field heading ${ JSON . stringify ( expectedHeading ) } , found ${ JSON . stringify ( fieldStart . line . raw ) } ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
const fieldEnd = fieldStarts [ fieldIndex + 1 ] ? . index ? ? entries . length
const fieldEntries = entries . slice ( fieldStart . index , fieldEnd )
const value = fieldEntries [ 1 ]
if ( value === undefined || /^#{1,6} / . test ( value . raw ) || value . raw . trim ( ) . length === 0 ) {
failures . push ( { path : readme , message : ` line ${ fieldStart . line . index } : ${ expectedHeading } requires one non-empty paragraph ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
if ( value . index !== fieldStart . line . index + 2 ) {
failures . push ( { path : readme , message : ` line ${ fieldStart . line . index } : ${ expectedHeading } and its paragraph require one blank line between them ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
const unexpected = fieldEntries . slice ( 2 ) . find ( line = > ! /^##### \S/ . test ( line . raw ) )
if ( unexpected !== undefined ) {
failures . push ( { path : readme , message : ` line ${ unexpected . index } : content after ${ expectedHeading } paragraph must be a titled H5 plus \` markdown \` fence owned by that field ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
const nextHeadingLine = fieldStarts [ fieldIndex + 1 ] ? . line . index
2026-07-24 19:54:25 +08:00
? ? entryStarts [ entryIndex + 1 ] ? . line . index
2026-07-19 18:08:42 +08:00
? ? nextH2Line
if ( rawLines [ nextHeadingLine - 2 ] ? . trim ( ) . length !== 0 ) {
failures . push ( { path : readme , message : ` line ${ nextHeadingLine } : Model Experience headings require a preceding blank line ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
const verbatim = validateNestedVerbatim ( rawLines . slice ( value . index , nextHeadingLine - 1 ) , verbatimFragments )
if ( verbatim . error !== undefined ) {
failures . push ( { path : readme , message : ` line ${ value . index } : ${ verbatim . error } ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
if ( fieldEntries . length - 2 !== verbatim . blocks ) {
failures . push ( { path : readme , message : ` line ${ value . index } : every nested H5 must own exactly one \` markdown \` fence ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-19 18:08:42 +08:00
break
}
parsedFields . push ( { value , verbatimBlocks : verbatim.blocks } )
2026-07-14 00:22:52 +08:00
}
2026-07-24 19:54:25 +08:00
if ( entryError ) break
2026-07-19 18:08:42 +08:00
const modelViewField = parsedFields [ 0 ] as ParsedField
const tokenEffectField = parsedFields [ 1 ] as ParsedField
const kvCacheEffectField = parsedFields [ 2 ] as ParsedField
const modelView = modelViewField . value
const tokenEffect = tokenEffectField . value
const kvCacheEffect = kvCacheEffectField . value
2026-07-19 17:39:50 +08:00
if ( /\]\(#[^)]+\)/ . test ( modelView . raw ) || /\]\(#[^)]+\)/ . test ( tokenEffect . raw ) || /\]\(#[^)]+\)/ . test ( kvCacheEffect . raw ) ) {
2026-07-19 18:08:42 +08:00
failures . push ( { path : readme , message : ` line ${ heading . index } : Model Experience fields must not link between local subsections; nest the H5 in its owning H4 field ` } )
2026-07-24 19:54:25 +08:00
entryError = true
2026-07-14 00:22:52 +08:00
break
}
2026-07-24 19:54:25 +08:00
entryFragments . add ( fragment )
modelContextEntries . push ( {
2026-07-19 18:08:42 +08:00
heading ,
modelView ,
tokenEffect ,
kvCacheEffect ,
title ,
modelViewVerbatimBlocks : modelViewField.verbatimBlocks ,
verbatimBlocks : parsedFields.reduce ( ( total , field ) = > total + field . verbatimBlocks , 0 ) ,
} )
2026-07-12 02:12:36 +08:00
}
2026-07-24 19:54:25 +08:00
if ( entryError ) continue
2026-07-13 22:26:33 +08:00
2026-07-24 19:54:25 +08:00
const promptWithoutVerbatim = modelContextEntries . find ( entry = > isDirectSystemPromptEntry ( entry . title )
&& entry . modelViewVerbatimBlocks === 0 )
2026-07-14 00:22:52 +08:00
if ( promptWithoutVerbatim !== undefined ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : ` line ${ promptWithoutVerbatim . heading . index } : system-prompt entry must contain a titled H5 plus verbatim \` markdown \` block under ${ MODEL_VIEW_HEADING } ` } )
2026-07-13 21:33:23 +08:00
continue
}
2026-07-24 19:54:25 +08:00
const hasConcreteLiteral = modelContextEntries . some ( entry = > entry . verbatimBlocks > 0
|| entry . modelView . raw . includes ( '`' )
|| entry . tokenEffect . raw . includes ( '`' )
|| toolCatalogLinkFragments ( entry . modelView . raw ) . length > 0 )
2026-07-14 00:22:52 +08:00
if ( ! hasConcreteLiteral ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : 'structured Model Experience must ground at least one entry with inline code, a nested `markdown` block, or an anchored tool-catalog link' } )
2026-07-13 21:33:23 +08:00
continue
}
2026-07-14 00:22:52 +08:00
let catalogError = false
2026-07-24 19:54:25 +08:00
for ( const entry of modelContextEntries ) {
if ( ! /\bschemas?\b/i . test ( entry . title ) ) continue
const fragments = toolCatalogLinkFragments ( entry . modelView . raw )
2026-07-14 00:22:52 +08:00
if ( fragments . length === 0 ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : ` line ${ entry . heading . index } : tool-schema entry must link an anchored section of ../../../docs/tool-catalog.md ` } )
2026-07-14 00:22:52 +08:00
catalogError = true
break
}
const invalid = fragments . find ( fragment = > ! toolCatalogFragments . has ( fragment ) )
if ( invalid !== undefined ) {
2026-07-24 19:54:25 +08:00
failures . push ( { path : readme , message : ` line ${ entry . modelView . index } : tool-catalog link fragment ${ JSON . stringify ( invalid ) } does not name an H2 section ` } )
2026-07-14 00:22:52 +08:00
catalogError = true
break
}
2026-07-13 15:47:46 +08:00
}
2026-07-14 00:22:52 +08:00
if ( catalogError ) continue
2026-07-24 19:54:25 +08:00
verbatimBlockCount += modelContextEntries . reduce ( ( total , entry ) = > total + entry . verbatimBlocks , 0 )
modelContextEntryCount += modelContextEntries . length
systemPromptEntryCount += modelContextEntries . filter ( entry = > isDirectSystemPromptEntry ( entry . title ) ) . length
toolSchemaEntryCount += modelContextEntries . filter ( entry = > /\bschemas?\b/i . test ( entry . title ) ) . length
kvCacheEffectCount += modelContextEntries . length
2026-07-13 22:26:33 +08:00
structuredCount += 1
2026-07-12 02:12:36 +08:00
}
if ( failures . length === 0 ) {
2026-07-24 19:54:25 +08:00
console . log ( ` verify-package-readme-model-experience: ${ packageJsons . length } README(s) checked ( ${ omittedSectionCount } audited omissions, ${ structuredCount } structured, ${ modelContextEntryCount } model-context entries, ${ kvCacheEffectCount } KV-cache fields, ${ systemPromptEntryCount } fenced system-prompt entries, ${ toolSchemaEntryCount } catalog-linked tool-schema entries, ${ explainedNoneCount } explained none, ${ indirectCount } indirect, ${ verbatimBlockCount } verbatim markdown blocks), all conform. ` )
2026-07-12 02:12:36 +08:00
process . exit ( 0 )
}
console . error ( 'verify-package-readme-model-experience failed:' )
for ( const failure of failures ) {
console . error ( ` ${ relative ( root , resolve ( root , failure . path ) ) } : ${ failure . message } ` )
}
process . exit ( 1 )